【问题标题】:Angular select all checkboxes from outside ng-repeatAngular 从 ng-repeat 外部选择所有复选框
【发布时间】:2015-07-09 04:00:01
【问题描述】:

说明

我有一个小型产品订单系统,用户可以在其中添加订单行,并在每个订单行上添加一个或多个产品。 (我意识到多个产品在同一订单线上是很不寻常的,但这是另一个问题。

每行可以选择的产品基于产品的层次结构。例如:

示例产品展示

T-Shirts
   V-neck
   Round-neck
   String vest

JSON 数据

$scope.products = [
{ 
  id: 1, 
  name: 'T Shirts', 
  children: [
    { id: 4, name: 'Round-neck', children: [] },
    { id: 5, name: 'V-neck', children: [] },
    { id: 6, name: 'String vest (exclude)', children: [] }
  ] 
},
{ 
  id: 2, 
  name: 'Jackets', 
  children: [
    { id: 7, name: 'Denim jacket', children: [] },
    { id: 8, name: 'Glitter jacket', children: [] }
  ] 
},
{ 
  id: 3, 
  name: 'Shoes', 
  children: [
    { id: 9, name: 'Oxfords', children: [] },
    { id: 10, name: 'Brogues', children: [] },
    { id: 11, name: 'Trainers (exclude)', children: []}
  ] 
}

];

T 恤不可选,但 3 个子产品可以。

我正在努力实现的目标

我想做的是有一个“全选”按钮,它会自动将三种产品添加到订单行中。

次要要求是,当按下“全选”按钮时,它会根据产品的 ID 排除某些产品。我为此创建了一个“排除”数组。

我已经设置了一个Plunker 来说明购物车以及我想要做什么。

到目前为止它可以:

  • 添加/删除订单行
  • 添加/删除产品
  • 为部分中的所有产品添加“检查”,不包括“排除”数组中的任何产品

问题

但是,虽然它在输入中添加了检查,但它不会触发输入上的 ng-change:

<table class="striped table">
    <thead>
      <tr>
        <td class="col-md-3"></td>
        <td class="col-md-6"></td>
        <td class="col-md-3"><a ng-click="addLine()" class="btn btn-success">+ Add order line</a></td>
      </tr>
    </thead>
    <tbody>
      <tr ng-repeat="line in orderHeader.lines">
        <td class="col-md-3">

          <ul>
            <li ng-repeat="product in products" id="line_{{ line.no }}_product_{{ product.id }}">

              {{ product.name }} <a ng-click="selectAll(product.id, line.no)" class="btn btn-primary">Select all</a>

              <ul>
               <li ng-repeat="child in product.children">
                 <input type="checkbox" 
                      ng-change="sync(bool, child, line)" 
                      ng-model="bool" 
                      data-category="{{child.id}}" 
                      id="check_{{ line.no }}_product_{{ child.id }}"
                      ng-checked="isChecked(child.id, line)">
              {{ child.name }}
               </li> 
              </ul>

            </li>
          </ul>
        </td>
        <td class="col-md-6">
          <pre style="max-width: 400px">{{ line }}</pre>
        </td>
        <td class="col-md-3">
          <a ng-click="removeLine(line)" class="btn btn-warning">Remove line</a>
        </td>
      </tr>
    </tbody>
  </table>

Javascript

$scope.selectAll = function(product_id, line){

  target = document.getElementById('line_'+line+'_product_'+product_id);

  checkboxes = target.getElementsByTagName('input');

  for (var i = 0; i < checkboxes.length; i++) {
    if (checkboxes[i].type == 'checkbox') {

      category = checkboxes[i].dataset.category;

      if($scope.excluded.indexOf(parseInt(category)) == -1)
      {
        checkboxes[i].checked = true;
        // TODO: Check the checkbox, and set its bool parameter to TRUE     
      }
    }
  }
}

更新完整解决方案

上面的代码有几个问题。首先,我试图通过操纵 DOM 来解决这个问题,这与 Angular 试图实现的目标非常矛盾。

所以解决方案是在产品上添加一个“已检查”属性,以便我可以跟踪它们是否包含在订单行中,然后视图会自动更新。

此方法的一个缺点是负载会大得多(除非在发送到后端 API 之前对其进行过滤),因为现在每个订单行都有所有产品的数据,即使它们没有被选中。

另外,让我感到困惑的一点是忘记了 Javascript 传递对象/数组的 references,而不是新副本。

解决方案

Javascript

 var myApp = angular.module('myApp', []);

myApp.controller('CartForm', ['$scope', function($scope) {

  var inventory = [
{ 
  id: 1, 
  name: 'T Shirts',
  checked: false, 
  children: [
    { id: 4, name: 'Round-neck', checked: false, children: [] },
    { id: 5, name: 'V-neck', checked: false, children: [] },
    { id: 6, name: 'String vest (exclude)', checked: false, children: [] }
  ] 
},
{ 
  id: 2, 
  name: 'Jackets',
  checked: false, 
  children: [
    { id: 7, name: 'Denim jacket', checked: false, children: [] },
    { id: 8, name: 'Glitter jacket', checked: false, children: [] }
  ] 
},
{ 
  id: 3, 
  name: 'Shoes', 
  checked: false, 
  children: [
    { id: 9, name: 'Oxfords', checked: false, children: [] },
    { id: 10, name: 'Brogues', checked: false, children: [] },
    { id: 11, name: 'Trainers (exclude)', checked: false, children: []}
  ] 
}
   ];

  $scope.debug_mode = false;


  var products = angular.copy(inventory);

  $scope.orderHeader = {
order_no: 1,
total: 0,
lines: [
  {
    no: 1,
    products: products,
    total: 0,
    quantity: 0
  }
]
  };


  $scope.excluded = [6, 11];

   $scope.addLine = function() {

 var products = angular.copy(inventory);

  $scope.orderHeader.lines.push({
      no: $scope.orderHeader.lines.length + 1,
      products: products,
      quantity: 1,
      total: 0
  });

  $scope.loading = false;

}


    $scope.removeLine = function(index) {
  $scope.orderHeader.lines.splice(index, 1);
}  


$scope.selectAll = function(product){

  angular.forEach(product.children, function(item){
    if($scope.excluded.indexOf(parseInt(item.id)) == -1) {
        item.checked=true;
    }
  });

}

$scope.removeAll = function(product){

  angular.forEach(product.children, function(item){
    item.checked=false;
  });

}

$scope.toggleDebugMode = function(){
  $scope.debug_mode = ($scope.debug_mode ? false : true);
}


}]);

Click here to see the Plunker

【问题讨论】:

  • 你可以使用 Angular 来检查或取消选中而不是更改 DOM 吗?

标签: javascript angularjs angularjs-ng-repeat


【解决方案1】:

你真的把事情复杂化了

考虑这种通过ng-model向每个产品添加checked属性的简化

<!-- checkboxes -->
<li ng-repeat="child in product.children">
       <input ng-model="child.checked"  >
</li>

如果向项目本身添加属性不切实际,您始终可以为选中的属性保留另一个数组,该数组与子数组具有匹配的索引。为此在 ng-repeat 中使用 $index

并将整个对象传递给selectAll()

<a ng-click="selectAll(product,line)">

这允许在控制器中做:

$scope.selectAll = function(product, line){      
  angular.forEach(product.children, function(item){
       item.checked=true;
  });
  line.products=product.children;      
}

使用 Angular,您需要始终首先考虑操作数据模型,然后让 Angular 管理 DOM

强烈建议阅读:"Thinking in AngularJS" if I have a jQuery background?

DEMO

【讨论】:

  • 如果您单击任何按钮上的“全选”,然后取消选中任何项目,该项目将从屏幕中删除。
  • @Werlang 我没有尝试重写整个应用程序。我只修改了答案中提到的功能,并且可能在其余部分中引入了错误。随意修改其余部分,但这意味着基本上重新开始
  • @Werlang 还试图教一些关于改变思维方式的知识,这是 SO 的重点,而不仅仅是提供可以完成所有工作的代码
  • 我快到了。我可以让全选/无选择工作,但是当有多个订单行时它不起作用。在第一行中选择会将产品添加到所有其他行中。我在这里更新了 Plunker:plnkr.co/edit/7t2bwjJaCs0gjXz80FQt?p=preview
  • 用户界面似乎可以正常工作。如果对您有帮助,请随时接受答案。如果还有更多问题,我看不出它们是什么
【解决方案2】:

为什么以编程方式选中复选框时不触发 ng-change?

这是因为

  if($scope.excluded.indexOf(parseInt(category)) == -1)
  {
    checkboxes[i].checked = true;
    // TODO: Check the checkbox, and set its bool parameter to TRUE     
  }

仅影响视图 (DOM)。 ng-changengModel 一起工作,它们无法意识到复选框在视觉上确实发生了变化。

我建议您参考我在How can I get angular.js checkboxes with select/unselect all functionality and indeterminate values? 提供的解决方案,它适用于您拥有的任何模型结构(有些人可能将此称为Angular 方式)。

【讨论】:

    猜你喜欢
    • 2015-05-14
    • 1970-01-01
    • 1970-01-01
    • 2014-07-12
    • 2015-06-14
    • 2015-09-09
    • 2015-04-10
    • 1970-01-01
    • 2019-04-30
    相关资源
    最近更新 更多