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>

但是,我需要复选框表示的值映射到不同的模型(不仅仅是 2 路绑定到 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 逻辑和验证垃圾,我就已经可以完成了。写这些复杂的指令,让未来的开发者更加困惑,比我写的 jQuery 代码更愚蠢,99% 的人一眼就能理解?其他开发人员如何划清界限?

我看到整个堆栈溢出。例如,这个问题似乎可以用十几行简单的 jQuery 来回答,但他选择了有角度的方式,有一个指令和一个部分......对于一个简单的问题似乎需要做很多工作.

我不希望这个问题违反规则,所以具体来说,我想我想知道:我应该如何编写检查是否已选择“无”的代码(如果它作为这组中的一个选项存在)复选框),然后相应地选中/取消选中其他框?更复杂的指令?我不敢相信我是唯一一个为了满足一个固执己见的框架而必须实现比所需更复杂的代码的开发人员。我需要使用另一个实用程序库吗?

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

我还是 AngularJS 的新手,但在这种情况下,我想我会通过使用ng-click处理程序来解决它,或者在其他模型发生变化时$scope.$watch更新模型的状态。NONE

使用 ng-click

我制作了一个 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 回答