8

時代遅れで更新が必要な既存の企業のレガシー システムの寄せ集めを置き換えるために、新しい SPA フロント エンドを構築しています。私はAngularが初めてで、コミュニティが私にいくつかの視点を与えてくれるかどうかを見たいと思っていました. 私の問題を述べてから、質問をします。

.js次のようなデータを使用して、インクルードからのデータに基づいていくつかの一連のチェック ボックスを生成する必要があります。

$scope.fieldMappings.investmentObjectiveMap = [
  {'id':"CAPITAL PRESERVATION", 'name':"Capital Preservation"},
  {'id':"STABLE", 'name':"Moderate"},
  {'id':"BALANCED", 'name':"Moderate Growth"},
   // etc
  {'id':"NONE", 'name':"None"}
];

ng-repeatチェックボックスは、次のようにを使用して作成されます。

    <div ng-repeat="investmentObjective in fieldMappings.investmentObjectiveMap">
     ...
    </div>

ただし、チェックボックスで表される値を別のモデルにマップする必要がありました (fieldmappings オブジェクトに双方向でバインドされるだけではありません)。destarrayこれを実現するために、最終的にモデルにマップされる宛先配列を受け入れるディレクティブを作成しました。また、非常に特殊な GUI コントロールを処理する必要があることもわかっています。たとえば、他の項目がチェックされている場合は「なし」のチェックを外し、他のすべてがチェックされていない場合は「なし」をチェックします。checkedまた、チェックボックスのすべてのグループで「なし」がオプションになるわけではないため、ディレクティブは、すでにクリックされたものに基づいてチェックボックスグループの入力の状​​態をいじることができる検証関数を受け入れるのに十分汎用的である必要がありますが、十分にスマートです呼び出されたオプションがない場合に壊れないようにする"NONE". コントローラーで関数を呼び出す ng-click を追加することから始めましたが、スタック オーバーフローを調べたところ、コントローラー内に DOM 操作コードを配置するのは悪いと言っている人を読みました。それはディレクティブに入れる必要があります。では、別のディレクティブが必要ですか?

これまでのところ: (html):

      <input my-checkbox-group
              type="checkbox"
              fieldobj="investmentObjective"
              ng-click="validationfunc()"
              validationfunc="clearOnNone()"
              destarray="investor.investmentObjective" />

指令コード:

.directive("myCheckboxGroup", function () {
  return {
    restrict: "A",
    scope: {
      destarray:      "=",  // the source of all the checkbox values
      fieldobj:       "=",  // the array the values came from
      validationfunc: "&"   // the function to be called for validation (optional)
    },
    link: function (scope, elem, attrs) {
      if (scope.destarray.indexOf(scope.fieldobj.id) !== -1) {
        elem[0].checked = true;
      }
      elem.bind('click', function () {
        var index = scope.destarray.indexOf(scope.fieldobj.id);
        if (elem[0].checked) {
          if (index === -1) {
            scope.destarray.push(scope.fieldobj.id);
          }
        }
        else {
          if (index !== -1) {
            scope.destarray.splice(index, 1);
          }
        }
      });
    }
  };
})

.js コントローラー スニペット:

.controller( 'SuitabilityCtrl', ['$scope', function ( $scope ) {
  $scope.clearOnNone = function() {
    // naughty jQuery DOM manipulation code that
    // looks at checkboxes and checks/unchecks as needed
  };

上記のコードは完成し、正常に動作しますが、 のいたずらな jquery コードを除いて、clearOnNone()この質問を書いたのはそのためです。

そして、ここに私の質問があります。このすべての後、私は自分自身に考えます-コントローラーに記述されたjQueryを使用して、このすべてのGUIロジックと検証ジャンクを手動で処理した場合、私はすでに完了していた可能性があります。私たちの 99% が一目で理解できる jQuery コードを書いた場合よりも、将来の開発者が困惑しなければならないこれらの複雑なディレクティブを作成することが、どの時点でばかげたことになるのでしょうか? 他の開発者はどのように線を引いていますか?

これはスタックオーバーフロー全体に見られます。たとえば、この質問は、数十行の単純な jQuery で回答できるように見えますが、ディレクティブとパーシャルを使用して角度のある方法で回答することを選択しました...単純な問題の場合、多くの作業が必要なようです.

この質問がルールに違反することを望まないので、具体的には、「なし」が選択されているかどうかを確認するコードをどのように記述すべきかを知りたいと思います(このグループのオプションとして存在する場合チェックボックス)、それに応じて他のボックスをチェック/チェック解除しますか? より複雑なディレクティブですか? 独断的なフレームワークを満たすためだけに、必要以上に複雑なコードを実装しなければならない開発者が私だけだとは信じられません。使用する必要がある別の util ライブラリはありますか?

4

2 に答える 2

2

Jim の提案に従って、これを Programmers.StackExchange.com に投稿しました。それまでの間、私はトリッキーな DOM 操作をすべて処理するためのソリューションに落ち着きました。

私はそれを両方の方法で試しました-コントローラーでDOMイベントを処理し、ディレクティブを介して処理します:

(コントローラー経由) - .js コード:

 $scope.clearOnNone = function(groupName, $event) {
    var chkboxArr = $('input[name^=' + groupName + ']'),
        nonNoneValChecked = false,
        targetElem = null,
        labelText = "";

    // get the target of the click event by looking at the <label> sibling's text
    targetElem = event.target.nextElementSibling.textContent.trim();

    // if target was the None option, uncheck all others
    if (targetElem === "None") {
      chkboxArr.each(function() {
        labelText = this.nextElementSibling.textContent.trim();

        if (labelText !== "None") {
          this.checked = false;
        }
      });
    }
    // if the target was anything BUT the None option, uncheck None
    else {
      chkboxArr.each(function() {
        labelText = this.nextElementSibling.textContent.trim();

        if (labelText === "None") {
          this.checked = false;
        }
      });
    }
  };

(コントローラー経由) - html コード:

      <div ng-repeat="investmentObjective in fieldMappings.secondaryInvestmentObjectiveMap">
        <input checkbox-group
                type="checkbox"
                name="secondaryInvestmentObjective"
                ng-click="validationfunc('secondaryInvestmentObjective', $event)"
                validationfunc="clearOnNone('secondaryInvestmentObjective', $event)"
                fieldobj="investmentObjective"
                destarray="suitabilityHolder.suitability.secondaryInvestmentObjective" />
        <label class="checkbox-label"
                popover-title="{{investmentObjective.name}}"
                popover="{{investmentObjective.help}}"
                popover-trigger="mouseenter">{{investmentObjective.name}}
        </label>
      </div>

(コントローラー経由) - ディレクティブ コード:

.directive("checkboxGroup", function () {
  return {
    restrict: "A",
    scope: {
      destarray:      "=",  // the source of all the checkbox values
      fieldobj:       "=",  // the array the values came from
      validationfunc: "&"   // the function to be called for validation (optional)
    },
    link: function (scope, elem, attrs) {
      if (scope.destarray.indexOf(scope.fieldobj.id) !== -1) {
        elem[0].checked = true;
      }
      elem.bind('click', function () {
        var index = scope.destarray.indexOf(scope.fieldobj.id);
        if (elem[0].checked) {
          if (index === -1) {
            scope.destarray.push(scope.fieldobj.id);
          }
        }
        else {
          if (index !== -1) {
            scope.destarray.splice(index, 1);
          }
        }
      });
    }
  };
})

次に、その行が嫌いだと判断しましたevent.target.nextElementSibling.textContent.trim()...これらのメソッドがすべて存在することを再確認するか、try/catch. そこで、コントローラーからのロジックを含めるようにディレクティブを書き直しました。

(ディレクティブ経由) - html コード:

      <div ng-repeat="otherInvestment in fieldMappings.otherInvestmentsMap">
        <input type="checkbox"
                checkbox-group
                groupname="otherInvestment"
                labelvalue="{{otherInvestment.name}}"
                fieldobj="otherInvestment"
                destarray="suitabilityHolder.suitability.otherInvestment" />
        <label class="checkbox-label"
                popover-title="{{otherInvestment.name}}"
                popover="{{otherInvestment.help}}"
                popover-trigger="mouseenter">{{otherInvestment.name}}
        </label>
      </div>

(ディレクティブ経由) - ディレクティブ コード:

.directive("checkboxGroup", function () {
  return {
    restrict: "A",
    scope: {
      destarray:      "=",  // the source of all the checkbox values
      fieldobj:       "=",  // the array the values came from
      groupname:      "@",  // the logical name of the group of checkboxes
      labelvalue:     "@"   // the value that corresponds to this checkbox
    },
    link: function (scope, elem, attrs) {
      // Determine initial checked boxes
      // if the fieldobj.id exists in the destarray, check this checkbox
      if (scope.destarray.indexOf(scope.fieldobj.id) !== -1) {
        elem[0].checked = true;
      }

      // Update array on click
      elem.bind('click', function () {
        // store the index where the fieldobj.id exists in the destarray
        var index = scope.destarray.indexOf(scope.fieldobj.id),
            // get the array of checkboxes that form this checkbox group
            chkboxArr = $('input[groupname^=' + scope.groupname + ']');

        // Add if checked
        if (elem[0].checked) {
          if (scope.labelvalue === "None") {
            // loop through checkboxes and uncheck all the ones that are not "None"
            chkboxArr.each(function() {
              // have to noodle through the checkbox DOM element to get at its attribute list
              // - is there a cleaner way?
              var tmpLabelValue = this.attributes.labelvalue.nodeValue.trim();
              if (tmpLabelValue !== "None") {
                this.checked = false;
              }
            });
          }
          // if the target was anything BUT the None option, uncheck None
          else {
            chkboxArr.each(function() {
              var tmpLabelValue = this.attributes.labelvalue.nodeValue.trim();

              if (tmpLabelValue === "None") {
                this.checked = false;
              }
            });
          }

          if (index === -1) {
            // add the id to the end of the dest array
            // **will not maintain original order if several are unchecked then rechecked**
            scope.destarray.push(scope.fieldobj.id);
          }
        }

        // Remove if unchecked
        else {
          if (index !== -1) {
            scope.destarray.splice(index, 1);
          }
        }
      });
    }
  };
})

振り返ってみると、すべてのコードをディレクティブに格納することを好むと思いますが、jQuery を介してコントローラーですべての処理を行うよりも直感的ではなく複雑だと思います。コントローラから clearOnNone() 関数を切り取ります。つまり、この機能を処理するすべてのコードは、html マークアップとディレクティブにあります。

私はのようなコードのファンではありませんthis.attributes.labelvalue.nodeValue.trim(). 私のようなシナリオでは、ビジネス ユニットに特定の要件があり (他に言い方がありません)、退屈で面倒ですが、それをすべてコーディングする「クリーンな」方法があるかどうかはわかりません。

于 2013-10-08T19:03:21.790 に答える
0

ng-click私はまだAngularJSを初めて使用しますが、この場合、ハンドラーを使用するか、他のモデルが変更されるたびにモデル$scope.$watchの状態を更新することで解決すると思います。NONE

ng-クリックの使用

私はそれがどのように動作するかを示すjsFiddleを作成しましたng-click:

http://jsfiddle.net/Dzj6K/1/

HTML:

<div ng-controller="myCtrl">

    <div ng-repeat="objective in objectives">
        <label><input type="checkbox" ng-model="objective.selected" ng-click="click(objective)" /> {{objective.name}}</label>
    </div>

</div>

JavaScript:

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

function myCtrl($scope) {

    $scope.objectives = [
        {'id':"CAPITAL PRESERVATION", 'name':"Capital Preservation"},
        {'id':"STABLE", 'name':"Moderate"},
        {'id':"BALANCED", 'name':"Moderate Growth"},
        {'id':"NONE", 'name':"None"}
    ];

    $scope.click = function(objective) {
        if (objective.id === "NONE") {
            if (objective.selected) {
                angular.forEach($scope.objectives, function(objective) {
                    if (objective.id !== "NONE") {
                        objective.selected = false;
                    }
                });
            }
        } else {
            angular.forEach($scope.objectives, function(objective) {
                if (objective.id === "NONE") {
                    objective.selected = false;
                }
            });
        }
    };

}

$scope.$watch の使用

そして、それがどのように動作するかを示す jsFiddle のバージョン$scope.$watch:

http://jsfiddle.net/Dzj6K/

HTML:

<div ng-controller="myCtrl">

    <div ng-repeat="objective in objectives">
        <label><input type="checkbox" ng-model="objective.selected" /> {{objective.name}}</label>
    </div>

</div>

JavaScript:

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

function myCtrl($scope) {

    $scope.objectives = [
        {'id':"CAPITAL PRESERVATION", 'name':"Capital Preservation"},
        {'id':"STABLE", 'name':"Moderate"},
        {'id':"BALANCED", 'name':"Moderate Growth"},
        {'id':"NONE", 'name':"None"}
    ];

    $scope.$watch('objectives', function() {
        var anySelected = false;
        var noneModel = null;

        angular.forEach($scope.objectives, function(objective) {
            if (objective.id === "NONE") {
                noneModel = objective;
            } else {
                anySelected = anySelected || objective.selected;
            }
        });

        if (noneModel) {
            noneModel.selected = !anySelected;
        }

    }, true);

}
于 2013-10-08T08:18:08.277 に答える