ng-repeat 与每个表格行的控制器:如何访问 x-editable 表单元素?

Posted

技术标签:

【中文标题】ng-repeat 与每个表格行的控制器:如何访问 x-editable 表单元素?【英文标题】:ng-repeat with controller for each table row: how do I access x-editable form elements? 【发布时间】:2015-12-16 07:40:34 【问题描述】:

我设置了一个与 x-editable 演示站点中的 Editable Row 示例非常相似的场景。在这种情况下,有一个简单的表,其中三列用于数据,第四列用于编辑和删除按钮。表格外的第三个按钮将一行添加到表格中。当表单可编辑时,数据列变为可编辑(x-editable 库的主要功能)。对于这个演示,第一列变成了简单的文本编辑,后两列变成了下拉列表。

表格是通过在行模板上使用 ng-repeat 来创建的。我需要做一些不同的事情,这些事情都涉及访问由 ng-repeat 创建的范围。我需要

检测该行何时可编辑,何时不可编辑 当第一个下拉列表更改时过滤第二个下拉列表的选项

为了尝试使用此演示,我为单个行添加了一个控制器。这让我可以访问表单(name = rowform),但我仍然无法在“make”属性上设置监视。当用户进行选择时,我什至找不到表单的哪些属性正在发生变化。

如何在“make”属性上设置手表?

页面控制器

    angular.module('app').controller("quoteBuckingRaterController",
    function ($scope, $q, $filter, listService, transactionDataService) 

        $scope.equipment = []; 
        $scope.makes = []; 
        $scope.models = [];

        $scope.showModel = function(equip) 
            if(equip.model) 
                var selected = $filter('filter')($scope.models, id: equip.model);
                return selected.length ? selected[0].name : 'Not set';
             else 
                return 'Not set';
            
        ;

        $scope.showMake = function(equip) 
            if (equip.model) 
                var selected = $filter('filter')($scope.models,  id: equip.model );
                if (selected.length && selected.length > 0) 
                    if (equip.make != selected[0].make)
                        equip.make = selected[0].make;
                    return selected[0].make;
                
                else 
                    return 'Not set';
                
             else 
                return 'Not set';
            
        ;

        $scope.checkName = function (data, id) 
            if (!data) 
                return "Description is required";
            
        ;

        $scope.checkModel = function (data, id) 
            if (!data) 
                return "Model is required";
            
        ;

        $scope.saveEquipment = function (data, id) 
            $scope.inserted = null;
        ;

        $scope.cancelRowEdit = function (data, id) 
            $scope.inserted = null;
        ;

        $scope.removeEquipment = function(index) 
            $scope.equipment.splice(index, 1);
        ;

        $scope.addEquipment = function() 
            $scope.inserted = 
                id: $scope.equipment.length+1,
                name: '',
                make: null,
                model: null 
            ;
            $scope.equipment.push($scope.inserted);
        ;

        $scope.filterModels = function (make) 
            $scope.models = _.where($scope.allModels, function(item) 
                return item.make == make;
            );
        ;

        //called by another process when page loads
        $scope.initialize = function (loaded) 
            return $q(function (resolve, reject) 
                if (!loaded) 
                    listService.getEquipmentModels().then(function (data) 
                        $scope.allModels = data;
                        $scope.models = data;

                        //uses underscore.js
                        $scope.makes = _.chain(data)
                                        .map(function (item) 
                                            var m = 
                                                id: item.make,
                                                name: item.make
                                            ;
                                            return m;
                                        )
                                        .uniq()
                                        .value();                            
                        resolve();
                    );
                
            );
        
    );

行控制器

angular.module('app').controller("editRowController",
function ($scope) 
    $scope.testClick = function () 
        alert('button clicked');
    ;

    $scope.make = null;

    $scope.$watch('make', function () 
        alert('how do I tell when the make has been changed?');
        this.$parent.$parent.filterModels(make.id);
    );
);

html

<div>
    <div class="col-md-12" style="margin-bottom: 3px">
        <div class="col-md-4 col-md-offset-1" style="padding-top: 6px; padding-left: 0px"><label>Equipment</label></div>
        <div class="col-md-offset-10">
            <button class="btn btn-primary btn-sm" ng-click="addEquipment()">Add row</button>
        </div>
    </div>
    <div class="col-md-10 col-md-offset-1">    
        <table class="table table-bordered table-hover table-condensed">
            <tr style="font-weight: bold; background-color: lightblue">
                <td style="width:35%">Name</td>
                <td style="width:20%">Make</td>
                <td style="width:20%">Model</td>
                <td style="width:25%">Edit</td>
            </tr>
            <tr ng-repeat="equip in equipment" ng-controller="editRowController">
                <td>
                    <!-- editable equip name (text with validation) -->
                    <span editable-text="equip.name" e-name="name" e-form="rowform" onbeforesave="checkName($data, equip.id)" e-required>
                         equip.name || 'empty' 
                    </span>
                </td>
                <td>
                    <!-- editable make (select-local) -->
                    <span editable-select="equip.make" e-name="make" e-form="rowform" e-ng-options="s.value as s.name for s in makes">
                         showMake(equip) 
                    </span>
                </td>
                <td>
                    <!-- editable model (select-remote) -->
                    <span editable-select="equip.model" e-name="model" e-form="rowform" e-ng-options="g.id as g.name for g in models" onbeforesave="checkModel($data, equip.id)" e-required>
                         showModel(equip) 
                    </span>
                    <button type="button" ng-disabled="rowform.$waiting" ng-click="testClick()" class="btn btn-default">
                        test
                    </button>
                </td>
                <td style="white-space: nowrap">
                    <!-- form -->
                    <form editable-form name="rowform" onbeforesave="saveEquipment($data, equip.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == equip">
                        <button type="submit" ng-disabled="rowform.$waiting" class="btn btn-primary">
                            save
                        </button>
                        <button type="button" ng-disabled="rowform.$waiting" ng-click="rowform.$cancel()" class="btn btn-default">
                            cancel
                        </button>
                    </form>
                    <div class="buttons" ng-show="!rowform.$visible">
                        <button class="btn btn-primary" ng-click="rowform.$show()">edit</button>
                        <button class="btn btn-danger" ng-click="removeEquipment($index)">del</button>
                    </div>
                </td>
            </tr>
        </table>
    </div>
</div>

【问题讨论】:

一个工作的 jsfiddle 可能会有所帮助。 【参考方案1】:

ng-repeat creates a child scope for each row(每个equipment)。因此EditRowController 的作用域是父quoteBuckingRaterController 的子作用域。

这个 childScope 包含:

父作用域的所有属性(例如equipmentmakesmodels) 属性equip 具有equipment 数组的一个值,由ng-repeat 提供 在 ng-repeat 块内定义的任何附加范围属性,例如rowform

因此,您可以使用 $scope 变量访问 childController editRowController 中的这些属性,例如:

$scope.equip.make
$scope.equipment

并在 html 文件中的 ng-repeat 元素内使用 angular expression,例如:

equip.make
equipment

现在到$scope.$watch:如果您提供一个字符串作为第一个参数,这是一个类似于 html 文件中的角度表达式,只是没有括号equip.make 的示例:

$scope.$watch('equip.make', function (value) 
     console.log('equip.make value (on save): ' + value);
);

然而,angular-xeditable 仅在用户保存行时才更新equip.make 的值。如果你想实时观看用户输入,你必须使用 angular-xeditable 提供的 rowform 对象中的 $data 属性:

$scope.$watch('rowform.$data.make', function (value) 
    console.log('equip.make value (live): ' + value);
, true);

你也可以使用 ng-change:

<span editable-select="equip.make" e-name="make" e-ng-change="onMakeValueChange($data)" e-form="rowform" e-ng-options="s.value as s.name for s in makes">

JS:

$scope.onMakeValueChange = function(newValue) 
    console.log('equip.make value onChange: ' + newValue);

这应该可以解决您的第一个问题:如何查看make 属性。

您的第二个问题,如何检测该行何时可编辑,何时不可编辑,可以通过使用表单上的 onshow / onhide 属性或观察范围内rowform 对象的 $visible 属性来解决如documented in the angular-xeditable reference

<form editable-form name="rowform" onshow="setEditable(true)" onhide="setEditable(false)">

$scope.setEditable = function(value) 
      console.log('is editable? ' + value);
;

// or
$scope.$watch('rowform.$visible', function(value) 
  console.log('is editable? ' + value);
);

您可能会问为什么 rowform 对象在当前 childScope 中。 它由&lt;form&gt; 标签创建。 See the Angular Reference about the built-in form directive:

实例化 FormController 的指令。

如果指定了name属性,则发布表单控制器 到此名称下的当前作用域。

带有示例代码的工作 sn-p:

angular.module('app', ["xeditable"]);

angular.module('app').controller("editRowController", function ($scope) 
    $scope.testClick = function () 
        alert('button clicked');
    ;

    $scope.$watch('equip.make', function (value) 
        console.log('equip.make value (after save): ' + value);
    );
  
    $scope.$watch('rowform.$data.make', function (value) 
        console.log('equip.make value (live): ' + value);
    , true);
  
    // detect if row is editable by using onshow / onhide on form element
    $scope.setEditable = function(value) 
      console.log('is equip id ' + $scope.equip.id + ' editable? [using onshow / onhide] ' + value);
    ;
  
    // detect if row is editable by using a watcher on the form property $visible
    $scope.$watch('rowform.$visible', function(value) 
      console.log('is equip id ' + $scope.equip.id + ' editable [by watching form property]? ' + value);
    );
);


angular.module('app').controller("quoteBuckingRaterController", function ($scope, $filter) 
    $scope.equipment = []; 
    $scope.makes = [value: 1, name: 'Horst', value: 2, name: 'Fritz']; 
    $scope.models = [id: 1, name: 'PC', make: 1];

    $scope.showModel = function(equip) 
        if(equip.model) 
            var selected = $filter('filter')($scope.models, id: equip.model);
            return selected.length ? selected[0].name : 'Not set';
         else 
            return 'Not set';
        
    ;

    $scope.showMake = function(equip) 
        if (equip.model) 
            var selected = $filter('filter')($scope.models,  id: equip.model );
            if (selected.length && selected.length > 0) 
                if (equip.make != selected[0].make)
                    equip.make = selected[0].make;
                return selected[0].make;
            
            else 
                return 'Not set';
            
         else 
            return 'Not set';
        
    ;

    $scope.checkName = function (data, id) 
        if (!data) 
            return "Description is required";
        
    ;

    $scope.checkModel = function (data, id) 
        if (!data) 
            return "Model is required";
        
    ;

    $scope.saveEquipment = function (data, id) 
        $scope.inserted = null;
    ;

    $scope.cancelRowEdit = function (data, id) 
        $scope.inserted = null;
    ;

    $scope.removeEquipment = function(index) 
        $scope.equipment.splice(index, 1);
    ;

    $scope.addEquipment = function() 
        $scope.inserted = 
            id: $scope.equipment.length+1,
            name: '',
            make: null,
            model: null 
        ;
        $scope.equipment.push($scope.inserted);
    ;
);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-xeditable/0.1.9/js/xeditable.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/angular-xeditable/0.1.9/css/xeditable.css" rel="stylesheet"/>
<link href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet"/>
<div ng-app="app" ng-controller="quoteBuckingRaterController">
    <div class="col-md-12" style="margin-bottom: 3px">
        <div class="col-md-4 col-md-offset-1" style="padding-top: 6px; padding-left: 0px"><label>Equipment</label></div>
        <div class="col-md-offset-10">
            <button class="btn btn-primary btn-sm" ng-click="addEquipment()">Add row</button>
        </div>
    </div>
    <div class="col-md-10 col-md-offset-1">    
        <table class="table table-bordered table-hover table-condensed">
            <tr style="font-weight: bold; background-color: lightblue">
                <td style="width:35%">Name</td>
                <td style="width:20%">Make</td>
                <td style="width:20%">Model</td>
                <td style="width:25%">Edit</td>
            </tr>
            <tr ng-repeat="equip in equipment" ng-controller="editRowController">
                <td>
                    <!-- editable equip name (text with validation) -->
                    <span editable-text="equip.name" e-name="name" e-form="rowform" onbeforesave="checkName($data, equip.id)" e-required>
                         equip.name || 'empty' 
                    </span>
                </td>
                <td>
                    <!-- editable make (select-local) -->
                    <span editable-select="equip.make" e-name="make" e-form="rowform" e-ng-options="s.value as s.name for s in makes">
                         showMake(equip) 
                    </span>
                </td>
                <td>
                    <!-- editable model (select-remote) -->
                    <span editable-select="equip.model" e-name="model" e-form="rowform" e-ng-options="g.id as g.name for g in models" onbeforesave="checkModel($data, equip.id)" e-required>
                         showModel(equip) 
                    </span>
                    <button type="button" ng-disabled="rowform.$waiting" ng-click="testClick()" class="btn btn-default">
                        test
                    </button>
                </td>
                <td style="white-space: nowrap">
                    <!-- form -->
                    <form editable-form name="rowform" onbeforesave="saveEquipment($data, equip.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == equip" onshow="setEditable(true)" onhide="setEditable(false)">
                        <button type="submit" ng-disabled="rowform.$waiting" class="btn btn-primary">
                            save
                        </button>
                        <button type="button" ng-disabled="rowform.$waiting" ng-click="rowform.$cancel()" class="btn btn-default">
                            cancel
                        </button>
                    </form>
                    <div class="buttons" ng-show="!rowform.$visible">
                        <button class="btn btn-primary" ng-click="rowform.$show()">edit</button>
                        <button class="btn btn-danger" ng-click="removeEquipment($index)">del</button>
                    </div>
                </td>
            </tr>
        </table>
    </div>
</div>

【讨论】:

非常好的可编辑提示,但手表在编辑模式下无法工作。查看jsfiddle.net/Steve5877/32kzsvve 它是您发送的带有一些额外模型的代码,并在选择make 时尝试过滤模型。但是在编辑时进行更改不会触发手表。 我要奖励赏金,因为系统没有给我太多选择。无法延长赏金,即使直到最后一分钟都没有人看过这个。这个答案不完整,还没有工作,但它最接近并且显示出最大的努力。如果我不授予它,他无论如何都会得到一半的学分(除非我降级他),而我什么也得不到。我希望 Felix 在看到我仍然遇到的问题时会升级他的答案。 @SteveWash 我已经更新了我的答案和你的 jsFiddle:jsfiddle.net/32kzsvve/1 太棒了!谢谢,菲利克斯!您的第一个答案正是我自己一直在尝试的。很高兴我给了你赏金。【参考方案2】:

如果你只是想$watchmakeequipment 的属性,请尝试更改为:

$scope.$watch('equipment.make', function()(...))

【讨论】:

【参考方案3】:

您可以为此编写自己的指令。

主要优点是指令具有独立的范围并且可以拥有自己的控制器。

请参阅the directive documentation 了解它是否适​​合您。

【讨论】:

以上是关于ng-repeat 与每个表格行的控制器:如何访问 x-editable 表单元素?的主要内容,如果未能解决你的问题,请参考以下文章

如何将第二个表格视图的值与第一个表格视图的单击行的关系分开?

AngularJS - 如何从控制器中的ng-repeat访问下一个项目

如何在不允许表为空的情况下删除ng-repeat表行?

ng-repeat:访问对象数组中每个对象的键和值

AngularJS ng-repeat 与表格内的自定义元素呈现奇怪

在 ng-repeat 指令中创建控制器