27

私はこれらのようなものを探しています(「親」のあるトライステートチェックボックス)。しかし、今はjQueryに依存していないため、このソリューションを使用するのは洗練されていません。モデルに自動的に(チェックされていない)チェックボックスがオンになっているjQueryがクリックされたことを認識させるには、$scope。$applyを呼び出す必要があります。

これは、ng-indeterminate-valueの実装を要求するangular.jsのバグです。しかし、それでもすべての子に同期を与えることはできません。これは、コントローラーの一部である必要はないと思います。

私が探しているのは次のようなものです。

  • 次のような構文を持つ「ng-children-model」ディレクティブ<input type="checkbox" ng-children-model="child.isSelected for child in listelements">。ブール値のリストが計算され、0が選択されている場合->チェックボックスはfalseです。すべて選択した場合->チェックボックスがtrue。それ以外の場合->チェックボックスは不定です。
  • 私のコントローラーでは、次のようなものがあります。$scope.listelements = [{isSelected: true, desc: "Donkey"},{isSelected: false, desc: "Horse"}]
  • チェックボックスは通常どおりに作成されます<tr ng-repeat="elem in listelements"><td><input type="checkbox" ng-model="elem.isSelected"></td><td>{{elem.desc}}</td></tr>
  • 私が理解しているように、ブラウザはクリックされた不確定チェックボックスがどの状態になるかを決定します。
4

8 に答える 8

20

あなたが提供するサンプルソリューションは、コントローラーにあまりにも多くのコードを入れていると思います。コントローラは実際にはリストのみを気にする必要があり、HTML /ディレクティブが表示を処理する必要があります([すべて選択]チェックボックスの表示を含む)。また、すべての状態の変化は、関数を作成することではなく、モデルを介して行われます。

Plunkerでソリューションをまとめました:http://plnkr.co/edit/gSeQL6XPaMsNSnlXwgHt?p = Preview

これで、コントローラーはリストを設定するだけです。

app.controller('MainCtrl', function($scope) {
    $scope.list = [{
        isSelected: true,
        desc: "Donkey"
    }, {
        isSelected: false,
        desc: "Horse"
    }];
});

ビューは単にそれらをレンダリングします:

<div ng-repeat="elem in list">
  <input type="checkbox" ng-model="elem.isSelected" /> {{elem.desc}}
</div>

[すべて選択]チェックボックス用に、次のような新しいディレクティブを作成しましたcheckbox-all

  <input checkbox-all="list.isSelected" /> Select All

そして、それは使用に関する限りそれです、それはうまくいけば簡単です...その新しいディレクティブを書くことを除いて:

app.directive('checkboxAll', function () {
  return function(scope, iElement, iAttrs) {
    var parts = iAttrs.checkboxAll.split('.');
    iElement.attr('type','checkbox');
    iElement.bind('change', function (evt) {
      scope.$apply(function () {
        var setValue = iElement.prop('checked');
        angular.forEach(scope.$eval(parts[0]), function (v) {
          v[parts[1]] = setValue;
        });
      });
    });
    scope.$watch(parts[0], function (newVal) {
      var hasTrue, hasFalse;
      angular.forEach(newVal, function (v) {
        if (v[parts[1]]) {
          hasTrue = true;
        } else {
          hasFalse = true;
        }
      });
      if (hasTrue && hasFalse) {
        iElement.attr('checked', false);
        iElement.addClass('greyed');
      } else {
        iElement.attr('checked', hasTrue);
        iElement.removeClass('greyed');
      }
    }, true);
  };
});

変数はを2つの部分にparts分解するので、スコープからlist.isSelected値を取得できます。これは、各オブジェクトのプロパティです。listisSelected

プロパティをinput要素に追加しtype="checkbox"て、ブラウザの実際のチェックボックスにします。つまり、ユーザーはそれをクリックしたり、タブで移動したりできます。

チェックボックスはキーボードを介するなど、さまざまな方法で変更できるため、私はonchangeイベントではなくイベントにバインドします。onclickonchangeイベントは、の内部で実行scope.$apply()され、モデルの変更が最後にダイジェストされるようにします。

最後に$watch、チェックボックスを変更するための入力モデルを入力します(最後のモデルでは、true複雑なオブジェクトを監視できます)。つまり、チェックボックスがユーザーによって、またはその他の理由で変更された場合、[すべて選択]チェックボックスは常に同期されます。これは、ng-clickハンドラーをたくさん書くよりもはるかに優れています。

チェックボックスがオンとオフの両方の場合、マスターチェックボックスをオフに設定し、スタイル「灰色」を追加します(を参照style.css)。そのCSSスタイルは基本的に不透明度を30%に設定し、チェックボックスを灰色で表示しますが、それでもクリック可能です。タブで移動し、スペースバーを使用して値を変更することもできます。

Firefox、Chrome、Safariでテストしましたが、IEを持っていません。うまくいけば、これはあなたのために働きます。

于 2013-02-10T22:20:53.723 に答える
20

新しいタイプ/種類のコンポーネントが必要なので、これはカスタムディレクティブの良いケースのように聞こえます。
親/マスター/トライステートチェックボックスと個々のデュアルステートチェックボックスは相互に作用する必要があるため、ロジックを処理するために、独自のコントローラーを備えた単一のディレクティブをお勧めします。

<tri-state-checkbox checkboxes="listelements"></tri-state-checkbox>

指令:

app.directive('triStateCheckbox', function() {
  return {
    replace: true,
    restrict: 'E',
    scope: { checkboxes: '=' },
    template: '<div><input type="checkbox" ng-model="master" ng-change="masterChange()">'
      + '<div ng-repeat="cb in checkboxes">'
      + '<input type="checkbox" ng-model="cb.isSelected" ng-change="cbChange()">{{cb.desc}}'
      + '</div>'
      + '</div>',
    controller: function($scope, $element) {
      $scope.masterChange = function() {
        if($scope.master) {
          angular.forEach($scope.checkboxes, function(cb, index){
            cb.isSelected = true;
          });
        } else {
          angular.forEach($scope.checkboxes, function(cb, index){
            cb.isSelected = false;
          });
        }
      };
      var masterCb = $element.children()[0];
      $scope.cbChange = function() {
        var allSet = true, allClear = true;
        angular.forEach($scope.checkboxes, function(cb, index){
          if(cb.isSelected) {
            allClear = false;
          } else {
            allSet = false;
          }
        });
        if(allSet)        { 
          $scope.master = true; 
          masterCb.indeterminate = false;
        }
        else if(allClear) { 
          $scope.master = false; 
          masterCb.indeterminate = false;
        }
        else { 
          $scope.master = false;
          masterCb.indeterminate = true;
        }
      };
      $scope.cbChange();  // initialize
    },
  };
});

ニーズに合わせてテンプレートを変更するか、templateUrlで外部テンプレートを使用してください。

isSelectedディレクティブは、checkboxs配列にプロパティとプロパティを持つオブジェクトが含まれていることを前提としていdescます。

プランカー

更新:ディレクティブでトライステートチェックボックスのみをレンダリングする場合、つまり個々のチェックボックスがHTMLに含まれている場合(@Piranのソリューションなど)、そのための別のプランカーバリエーションがあります。このプランカーの場合、HTMLは次のようになります。

<tri-state-checkbox checkboxes="listelements" class="select-all-cb">
</tri-state-checkbox>select all
<div ng-repeat="item in listelements">
   <input type="checkbox" ng-model="item.isSelected"> {{item.desc}}
</div>
于 2013-02-11T21:06:26.627 に答える
4

これがPiranのソリューションの洗練されたバージョンです。.prop()代わりに使用すると.attr()、問題が修正されcheckedます。

使用法:

<div ng-repeat="elem in list">
    <input type="checkbox" ng-model="elem.isSelected" /> {{elem.desc}}
</div>
<ui-select-all items="list" prop="isSelected"></ui-select-all> Select all
于 2013-03-28T17:40:01.813 に答える
2

ある種のDOM操作を行う必要がある場合、またはDOMの操作動作の多くを「再利用可能な」コンポーネントに抽象化したい場合にのみ、ディレクティブを作成する必要があると思います。

これはあなたが試みていたのと同じことを達成する解決策ですが、これはコントローラーのロジックのみを実行します...コントローラーを無駄のない状態に保ちたい場合は、このすべてのロジックをサービスにプッシュすることができます... A複数の場所でこれを再利用したい場合は、サービスもこれを行うのに適した場所です。

http://plnkr.co/edit/hNTeZ8Tuht3T9NuY7HRi?p=preview

コントローラにはDOM操作がないことに注意してください。Angularで提供される一連のディレクティブを使用して、必要な効果を達成しています。新しいディレクティブは必要ありません。ロジックを抽象化するためにディレクティブを使用する必要はないと思います。

お役に立てれば..

于 2013-02-11T13:30:50.200 に答える
1

ng-modelがブールモデル(たとえば、Y / N、 '0' / '1')に割り当てられていると想定できない場合、および/または独自のマークアップを使用したい場合は、ngModel機能を活用してHTML構造についての仮定は、私見より良いものではありません。

例: http: //plnkr.co/edit/mZQBizF72pxp4BvmNjmj?p = Preview

使用例:

  <fieldset indeterminate-group>
    <legend>Checkbox Group</legend>
    <input type="checkbox" name="c0" indeterminate-cue> Todos <br>
    <input type="checkbox" name="c1" ng-model="data.c1" ng-true-value="'Y'" ng-false-value="'F'" indeterminate-item> Item 1 <br>
    <input type="checkbox" name="c2" ng-model="data.c2" ng-true-value="'Y'" ng-false-value="'F'" indeterminate-item> Item 2 <br>
    <input type="checkbox" name="c3" ng-model="data.c3" ng-true-value="'Y'" ng-false-value="'F'" indeterminate-item> Item 3 <br>
  </fieldset>

指令(主要部分):

angular.module('app', [])
  .directive('indeterminateGroup', function() {
    function IndeterminateGroupController() {
      this.items = [];
      this.cueElement = null;
    }
    ...
    function setAllValues(value) {
      if (this.inChangeEvent) return;

      this.inChangeEvent = true;
      try {
        this.items.forEach(function(item) {
          item.$setViewValue(value);
          item.$render();
        });
      } finally {
        this.inChangeEvent = false;
      }
    }

    return {
      restrict: "A",
      controller: IndeterminateGroupController,
      link: function(scope, element, attrs, ctrl) {
        ctrl.inputChanged = function() {
          var anyChecked = false;
          var anyUnchecked = false;
          this.items.forEach(function(item) {
            var value = item.$viewValue;
            if (value === true) {
              anyChecked = true;
            } else if (value === false) {
              anyUnchecked = true;
            }
          });

          if (this.cueElement) {
            this.cueElement.prop('indeterminate', anyChecked && anyUnchecked);
            this.cueElement.prop('checked', anyChecked && !anyUnchecked);
          }
        };
      }
    };
  })
  .directive('indeterminateCue', function() {
    return {
      restrict: "A",
      require: '^^indeterminateGroup',
      link: function(scope, element, attrs, indeterminateGroup) {
        indeterminateGroup.addCueElement(element);
        var inChangeEvent = false;
        element.on('change', function(event) {
          if (event.target.checked) {
            indeterminateGroup.checkAll();
          } else {
            indeterminateGroup.uncheckAll();
          }
        });
      }
    };
  })
  .directive('indeterminateItem', function() {
    return {
      restrict: "A",
      require: ['^^indeterminateGroup', 'ngModel'],
      link: function(scope, element, attrs, ctrls) {
        var indeterminateGroup = ctrls[0];
        var ngModel = ctrls[1];
        indeterminateGroup.addItem(ngModel);
        ngModel.$viewChangeListeners.push(function() {
          indeterminateGroup.inputChanged();
        });
      }
    };
  });

モデル:

// Bring your own model

TODO:

  • mainディレクティブコントローラー内のitem。$render()を削除します。
  • ディレクティブに適切な名前を付けます。
  • このディレクティブを複数のテーブル列で簡単に使用できるようにします。
于 2015-03-30T02:13:01.083 に答える
0

プランカー

"use strict";

var module = angular.module("myapp", []);

function Ctrl($scope) {
    var element = $("#select_all");
    $scope.$watch("$scope.isgreyed", $scope.fun = function() {
        element.prop("indeterminate", $scope.isgreyed);
    });
    $scope.list = [{
        isSelected: true,
        desc: "Donkey"
    }, {
        isSelected: false,
        desc: "Horse"
    }]
    $scope.isgreyed = true;
    $scope.master = false;
    $scope.onmasterclick = function() {
        $scope.list.map(function(v) {
            v.isSelected = $scope.master
        })
    }

    $scope.oncheckboxclick = function() {     
        if ($('.select_one:checked').length === 0) {
            $scope.isgreyed = false;
            $scope.master = false;     
        } else if ($('.select_one:not(:checked)').length === 0) {
            $scope.isgreyed = false;
            $scope.master = true;     
        } else {
            $scope.isgreyed = true;     
        }
        $scope.fun();
    }      
}

HTML:

<div ng-controller="Ctrl">
<table>
<tr>
  <td>
     <input type="checkbox" id="select_all" ng-model="master" ng-click="onmasterclick()">
  </td>
</tr>
<tr ng-repeat="elem in list">
  <td>
    <input ng-click="oncheckboxclick(elem)" class="select_one" type="checkbox" ng-model="elem.isSelected">
  </td>
  <td>{{elem.desc}}</td>
</tr>
</table>
</div>

はい、醜いです。

于 2012-09-30T02:31:20.297 に答える
0

Plnkerを使用して、リソースを消費するForEachやその他の複雑なものを使用せずに、少し優れたコードに書き直しました。

var app = angular.module('angularjs-starter', []);

app.controller('MainCtrl', function($scope) {
  $scope.listelements = [{
    isSelected: true,
    desc: "Donkey"
  }, {
    isSelected: false,
    desc: "Horse"
  }];
});

app.directive('triStateCheckbox', function() {
  return {
    replace: true,
    restrict: 'E',
    scope: {
      checkboxes: '='
    },
    template: '<input type="checkbox" ng-model="master" ng-change="masterChange()">',
    controller: function($scope, $element) {
      $scope.masterChange = function() {
        for(i=0;i<$scope.checkboxes.length; i++)
          $scope.checkboxes[i].isSelected=$scope.master;
      };
      $scope.$watch('checkboxes', function() {
        var set=0;
        for (i=0;i<$scope.checkboxes.length;i++)
          set += $scope.checkboxes[i].isSelected?1:0;
        $element.prop('indeterminate', false);
        $scope.master = (set === 0) ? false : true;
        if (set > 0 && set < i) {
          $scope.master = false;
          $element.prop('indeterminate', true);
        }
      }, true);
    }
  };
});
于 2013-11-20T12:29:09.967 に答える
0

私はそれがangularとjavascriptを組み合わせることで解決できると思います:

<div>

<input type="checkbox" id="select-all" name="selectAll" value="" ng-click="checkAll($event)" />

<div >
  <input type="checkbox"  name="childCheckbox" value=""  />

  <input type="checkbox"  name="childCheckbox" value=""  />

  <input type="checkbox"  name="childCheckbox" value=""  />

  <input type="checkbox"  name="childCheckbox" value=""  />

  <input type="checkbox"  name="childCheckbox" value=""  />

  <input type="checkbox"  name="childCheckbox" value=""  />
 </div>

</div>

checkAll()では、次のロジックがその役割を果たします

  $scope.checkAll = function (source) {
   checkboxes = document.getElementsByName('childCheckbox');                                                 
   for (var i = 0, n = checkboxes.length; i < n; i++)   {
     checkboxes[i].checked = source.originalEvent.srcElement.checked;
   }
于 2014-02-13T07:12:51.910 に答える