Pro/con使用Angular指令进行复杂的表单验证/ GUI操作

ten*_*gen 8 angularjs

我正在构建一个新的SPA前端,以取代现有企业遗留的过时且需要更新的系统大杂烩.我是棱角分明的新人,想看看社区能否给我一些看法.我会陈述我的问题,然后问我的问题.

我必须基于来自.jsinclude的数据生成几个复选框系列,其数据如下:

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

复选框是使用a创建的ng-repeat,如下所示:

    <div ng-repeat="investmentObjective in fieldMappings.investmentObjectiveMap">
     ...
    </div>
Run Code Online (Sandbox Code Playgroud)

但是,我需要复选框表示的值映射到不同的模型(不仅仅是2对方式绑定到fieldmappings对象).为此,我创建了一个指令,它接受一个destarray最终映射到模型的目标数组.我也知道我需要处理一些非常具体的gui控件,例如如果检查了其他任何内容则取消选中"None",或者如果其他所有内容都未选中则选中"None".此外,"无"在每组复选框中都不是一个选项,因此指令需要足够通用以接受验证函数,该函数可以checked根据已经点击的内容来调整复选框组输入的状态,但足够聪明如果没有调用选项,不要破坏"NONE".我开始通过添加一个在控制器中调用函数的ng-click来做到这一点,但是在查看堆栈溢出时,我读到人们说将DOM操作代码放在控制器中是不好的 - 它应该在指令中.所以我需要另一个指令吗?

到目前为止:(html):

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

指令代码:

.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);
          }
        }
      });
    }
  };
})
Run Code Online (Sandbox Code Playgroud)

.js控制器片段:

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

上面的代码完成并正常工作,除了顽皮的jquery代码clearOnNone(),这就是我写这个问题的原因.

这是我的问题:在所有这些之后,我想我自己 - 如果我只是用我的控制器中编写的jQuery手动处理所有这些GUI逻辑和验证垃圾,我就可以完成.在编写这些复杂的指令时,未来的开发人员将不得不解决的问题在于,如果我刚刚编写了jQuery代码,我们99%的人会一目了然地理解这些指令,那么在什么时候变得愚蠢呢?其他开发者如何划清界限?

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

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

ten*_*gen 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;
        }
      });
    }
  };
Run Code Online (Sandbox Code Playgroud)

(通过控制器)- 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>
Run Code Online (Sandbox Code Playgroud)

(通过控制器)-指令代码:

.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);
          }
        }
      });
    }
  };
})
Run Code Online (Sandbox Code Playgroud)

然后我决定我讨厌这些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>
Run Code Online (Sandbox Code Playgroud)

(通过指令)-指令代码:

.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);
          }
        }
      });
    }
  };
})
Run Code Online (Sandbox Code Playgroud)

回想起来,我想我更喜欢将所有代码放在一个指令中,尽管我认为它比通过 jQuery 将所有处理扔到控制器中更不直观且更复杂。它从控制器中删除了clearOnNone()函数,这意味着处理此功能的所有代码都在html标记和指令中。

我不喜欢像这样的代码this.attributes.labelvalue.nodeValue.trim(),我仍然在我的指令中使用了它。对于像我这样的场景,业务部门有某些要求(没有其他方式来表达)是乏味和麻烦的,我不知道是否真的有一种“干净”的方法来编码它。