表单验证 - 组中许多必需的一个

时间:2014-06-15 08:34:36

标签: angularjs

在我正在进行的项目中,我目前有三个文本框,我需要验证至少有一个文本框已经填充。

我一直在阅读使用Angular指令的自定义验证,我知道您可以使用以下命令设置指令链接函数中输入的有效性:

ctrl.$parsers.unshift(function(viewValue) {
  // validation logic here
});

我遇到的问题是我不需要设置单个输入的有效性。如果不符合标准,我需要使整个表单无效。我只是想知道如何处理这个问题?

我想也许我应该创建一个放在封闭表单上的指令,然后使表单无效?

我想我只是在寻找一些关于我应该怎么做的指导,因为我有点不清楚从哪里开始 - 我正在阅读的关于自定义验证的所有材料似乎都适用于你的验证特定输入而不是表单上的一组条件。

我希望自己清楚明白!感谢..

8 个答案:

答案 0 :(得分:42)

您可以使用 ng-required 强制用户通过检查字符串的长度属性来填充至少一个字段。

您可以执行以下操作:

<form name="myForm">
            <input type="text" ng-model="fields.one" name="firstField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" />
            <br/>
            <input type="text" name="secondField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" ng-model="fields.two" />
            <br/>
            <input type="text" ng-model="fields.three" name="thirdField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" />
            <br/>
            <button type="submit" ng-disabled="!myForm.$valid">Submit</button>
</form>

有关详细信息,请参阅此working fiddle example

通过阅读this question

,您可以获得有关必需 ng-required 的更多详细信息

答案 1 :(得分:23)

有几种方法,最佳选择取决于您的确切要求。

我发现这是一种通用且灵活的方法 通过&#34; generic&#34; 我的意思是它不仅适用于文本字段,也适用于其他类型的输入,例如复选框。
它的&#34;灵活&#34; 因为它允许任意数量的控制组,因此每个组的至少一个控件必须是非空的。此外,没有&#34;空间&#34;约束 - 每个组的控件可以在DOM内的任何位置(如果需要,很容易将它们限制在单个form内)。

该方法基于定义自定义指令(requiredAny),类似于ngRequired,但考虑了同一组中的其他控件。一旦定义,该指令可以像这样使用:

<form name="myForm" ...>
  <input name="inp1" ng-model="..." required-any="group1" />
  <input name="inp2" ng-model="..." required-any="group1" />
  <input name="inp3" ng-model="..." required-any="group1" />

  <input name="inp4" ng-model="..." required-any="group2" />
  <input name="inp5" ng-model="..." required-any="group2" />
</form>

<子> 在上面的示例中,[inp1,inp2,inp3]中的至少一个必须非空,因为它们属于group1
对于属于group2的[inp4,inp5]也是如此。


该指令如下所示:

app.directive('requiredAny', function () {
  // Map for holding the state of each group.
  var groups = {};

  // Helper function: Determines if at least one control
  //                  in the group is non-empty.
  function determineIfRequired(groupName) {
    var group = groups[groupName];
    if (!group) return false;

    var keys = Object.keys(group);
    return keys.every(function (key) {
      return (key === 'isRequired') || !group[key];
    });
  }

  return {
    restrict: 'A',
    require: '?ngModel',
    scope: {},   // An isolate scope is used for easier/cleaner
                 // $watching and cleanup (on destruction).
    link: function postLink(scope, elem, attrs, modelCtrl) {
      // If there is no `ngModel` or no groupName has been specified,
      // then there is nothing we can do.
      if (!modelCtrl || !attrs.requiredAny) return;

      // Get a hold on the group's state object.
      // (If it doesn't exist, initialize it first.)
      var groupName = attrs.requiredAny;
      if (groups[groupName] === undefined) {
        groups[groupName] = {isRequired: true};
      }

      var group = scope.group = groups[groupName];

      // Clean up when the element is removed.
      scope.$on('$destroy', function () {
        delete(group[scope.$id]);
        if (Object.keys(group).length <= 1) {
          delete(groups[groupName]);
        }
      });

      // Update the validity state for the 'required' error-key
      // based on the group's status.
      function updateValidity() {
        if (group.isRequired) {
          modelCtrl.$setValidity('required', false);
        } else {
          modelCtrl.$setValidity('required', true);
        }
      }

      // Update the group's state and this control's validity.
      function validate(value) {
        group[scope.$id] = !modelCtrl.$isEmpty(value);
        group.isRequired = determineIfRequired(groupName);
        updateValidity();
        return group.isRequired ? undefined : value;
      }

      // Make sure re-validation takes place whenever:
      //   either the control's value changes
      //   or the group's `isRequired` property changes
      modelCtrl.$formatters.push(validate);
      modelCtrl.$parsers.unshift(validate);
      scope.$watch('group.isRequired', updateValidity);
    }
  };
});

<子> 这可能不会那么短,但一旦包含在模块中,就很容易集成到表单中。


另请参阅此(不是) short demo

答案 2 :(得分:9)

现在为时已晚,但可能可以节省一些时间:

如果只有两个字段,并且想要使其中一个字段需要,那么

<input type="text" 
      ng-model="fields.one" 
      ng-required="!fields.two" />
<br/>
<input type="text" 
      ng-model="fields.two"
      ng-required="!fields.one"  />

如果您有三个问题,那么

<input type="text" 
      ng-model="fields.one" 
      ng-required="!(fields.two || fields.three)" />
<br/>
<input type="text" 
      ng-model="fields.two"
      ng-required="!(fields.one || fields.three)"  />
<br/>
<input type="text" 
      ng-model="fields.three" 
      ng-required="!(fields.one|| fields.two)" />

如果不止于此,我建议在示波器上编写一个函数并观察它。

See the working example

答案 3 :(得分:2)

修改ExpertSystem的答案(https://stackoverflow.com/a/24230876/4968547),以便他的代码在最新的angularjs中工作。

我更改了updateValidity()以将解析也设置为true / false

function updateValidity() {
            if (group.isRequired) {
                modelCtrl.$setValidity('required', false);
                modelCtrl.$setValidity('parse', false); 
            } else {
                modelCtrl.$setValidity('required', true);
                modelCtrl.$setValidity('parse', true);
            }
        }

现在它对我来说工作正常

答案 4 :(得分:1)

上周遇到同样的问题; ExpertSystem的解决方案是一个良好的开端,但我正在寻找一些改进:

  • 使用Angular 1.4.3
  • 使用ngMessages

我最终结束了this example on JSFiddle - 希望这有助于激励同一条船上的其他人!来自Fiddle的相关JS代码:

var app = angular.module('myApp', ['ngMessages']);
app.controller('myCtrl', function ($scope) {
    $scope.sendMessage = function () {
        $scope.myForm.$submitted = true;

        if ($scope.myForm.$valid) {
            alert('Message sent !');
        }
    };
});

app.directive('requiredAny', function () {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function postLink(scope, elem, attrs, ctrl) {
            // If there is no 'ngModel' or no groupName has been specified,
            // then there is nothing we can do
            if (!ctrl || !attrs.requiredAny) { return };

            // If this is the first time we've used this directive in this scope,
            // create a section for it's data. If you need / want to make use of
            // an isolate scope you'll need to make 'var groups' scoped to the directive;
            // but then you may want to look in to clearing out group entries yourself
            if (!scope.__requiredAnyGroups) {
                scope.__requiredAnyGroups = {}
            }
            var groups = scope.__requiredAnyGroups;

            // Create a bucket for this group if one does not yet exist
            if (!groups[attrs.requiredAny]) {
                groups[attrs.requiredAny] = {};
            }
            var group = groups[attrs.requiredAny];

            // Create the entry for this control
            group[attrs.ngModel] = {
                ctrl: ctrl,
                hasValue: false
            };

            ctrl.$validators.requiredAny = function(view, value) {
                var thisCtrl = group[attrs.ngModel],
                        ctrlValue = (typeof value !== 'undefined') && value,
                        oneHasValue = false;

                thisCtrl.hasValue = ctrlValue;

                // First determine if any field in the group has a value
                for (var prop in group) {
                    if (group.hasOwnProperty(prop) && group[prop].hasValue) {
                        oneHasValue = true;
                        break;
                    }
                }

                // Set the validity of all other fields based on whether the group has a value
                for (var prop in group) {
                    if (group.hasOwnProperty(prop) && thisCtrl != group[prop]) {
                        group[prop].ctrl.$setValidity('requiredAny', oneHasValue);
                    }
                }

                // Return the validity of this field
                return oneHasValue;
            };
        }
    };
});

答案 5 :(得分:0)

您可以为每个属性添加必需的属性,最后,您可以依赖每个/全部/或其中一个的验证

        <form name="form" novalidate ng-submit="submit()">
        // novalidate is form disabling your browser's own validation mechanism

          <input type="text" required ng-model="texts.text1"> 
          <input type="text" required ng-model="texts.text2"> 
          <input type="text" required ng-model="texts.text3"> 
          // you can do validation in variety of ways , but one of them is to disable your submit button until one of the textboxes are filled correctly like this : 

          <button type="submit" ng-disabled="form.text1.$invalid && form.text2.$invalid && form.text3.$invalid"></button>      

        </form>

这样,如果只填充其中一个,则启用按钮

我不知道你将如何表明该表格无效,但我认为取消提交按钮是一般方式

答案 6 :(得分:0)

以下是ExpertSystems精彩帖子的重构内容。我不需要破坏方法所以我去了它。

我还添加了一个灰色的解释,可能有助于您的代码。我将此指令用于所有必填字段。当我使用这个指令时,我不再使用ng-required或者required。

如果您想要一个必填字段,只需传入一个唯一的组名。如果您不想要所需的字段,则传入null,如果您想要有许多不同的组,只需传入匹配的组名。

我相信可以在这里完成更多的重构。 Angularjs声明当使用$ setValidity时,相反应该使用$ validators管道,但我无法使用它。我还在学习这种复杂的动物。如果您有更多信息,请发布!

app.directive('rsPartiallyRequired', function () {

 var allinputGroups = {};

 return {
   restrict: 'A',
   require: '?ngModel',
   scope: { },

   link: function(scope, elem, attrs, ctrl) {
     if( !ctrl || !attrs.rsPartiallyRequired ){ return } // no ngModel, or rsPartialRequired is null? then return.

    // Initilaize the following on load
    ctrl.$formatters.push( validateInputGroup ); // From model to view.
    ctrl.$parsers.unshift( validateInputGroup ); // From view to model.

    if ( ! allinputGroups.hasOwnProperty( attrs.rsPartiallyRequired )){ // Create key only once and do not overwrite it.
    allinputGroups[ attrs.rsPartiallyRequired ] = { isRequired: true } // Set new group name value to { isRequired: true }.
  }

    scope.inputGroup = allinputGroups[ attrs.rsPartiallyRequired ] // Pass { isRequired: true } to form scope.

    function validateInputGroup(value) {
    scope.inputGroup[ scope.$id ] = !ctrl.$isEmpty( value ); // Add to inputGroup ex: { isRequired: true, 01E: false }.
    scope.inputGroup.isRequired = setRequired( attrs.rsPartiallyRequired ); // Set to true or false.
    updateValidity(); // Update all needed inputs based on new user input.
    return scope.inputGroup.isRequired ? undefined : value
  }

    function setRequired(groupName) {
      if( ! allinputGroups[ groupName ] ){ return false } // No group name then set required to false.
      return Object.keys( allinputGroups[ groupName ] ).every( function( key ) { // Key is 'isRequired' or input identifier.
      return ( key === 'isRequired' ) || ! allinputGroups[ groupName ][ key ]
    });
  }

    scope.$watch('scope.inputGroup.isRequired', updateValidity); // Watch changes to inputGroup and update as needed.

    function updateValidity() { // Update input state validity when called.
      ctrl.$setValidity('required', scope.inputGroup.isRequired ? false : true );
    } 
  }
 }
});

// This directive sets input required fields for groups or individual inputs.  If an object in the template is given
// to the directive like this: 
// Object: { "name": "account_number", "attrs": { "required": { "group": "two"  }}}.
// HTML: <input type="text" rs-partially-required="{{ field.attrs.required.group }}" />
// Or anything where the evaluation is a string, for example we could use "groupOne" like this...
// HTML: <input type="text" rs-partially-required="groupOne" />
// Then this directive will set that group to required, even if it's the only member of group.  
// If you don't want the field to be required, simply give the directive a null value, like this...
// HTML: <input type="text" rs-partially-required="null" />
// However, when you want to use this directive say in an ngRepeat, then just give it a dynamic string for each input
// and link the inputs together by giving the exact matching string to each group that needs at least one field. ex:

// <input type="text" rs-partially-required="null" />
// <input type="text" rs-partially-required="one" />
// <input type="text" rs-partially-required="two" />
// <input type="text" rs-partially-required="one" />
// <input type="text" rs-partially-required="null" />
// <input type="text" rs-partially-required="three" />
// <input type="text" rs-partially-required="three" />
// <input type="text" rs-partially-required="three" />

// In the above example, the first and fifth input are not required and can be submitted blank.
// The input with group "two" is the only one in the group, so just that input will be required by itself.
// The 2 inputs with "one" will be grouped together and one or the other will require an input before
// the form is valid.  The same will be applied with group "three".
// For this form to be valid, group "two" will be required, and 1 input from group one will be required,  
// and 1 input from group three will be required before this form can be valid.

答案 7 :(得分:0)

我的项目中有类似的分组要求,我写了这篇文章。感兴趣的人可以使用这个

.directive('group',function(){
        return {
            require: '^form',
            link : function($scope,element,attrs,formCtrl){
                var ctrls =[];

                element.find(".group-member").each(function(){
                    var member = angular.element($(this));
                    var mdlCtrl = member.data("$ngModelController");
                    if(!mdlCtrl){
                        throw "Group member should have ng-model";
                    }
                    ctrls.push(mdlCtrl);
                });

                var errKey = attrs['name']+"GrpReqd";
                var min = attrs['minRequired'] || 1;
                var max = attrs['maxRequired'] || ctrls.length;

                $scope.validateGroup = function(){
                    var defined=0;
                    for(i=0;i<ctrls.length;i++){
                        if(ctrls[i].$modelValue){
                            defined++;
                        }
                    }
                    if(defined < min || defined > max){
                        formCtrl.$setValidity(errKey,false);
                    } else {
                        formCtrl.$setValidity(errKey,true);
                    }
                };

                //support real time validation
                angular.forEach(ctrls,function(mdlCtrl){
                    $scope.$watch(function () {
                          return mdlCtrl.$modelValue;
                       }, $scope.validateGroup);
                });

            }
        };
    })

HTML用法:

<div name="CancellationInfo" group min-required="1" max-required="1">
            <input type="text" class="form-control group-member" style="width:100%;" name="Field1" ng-model="data.myField"  />
            <input type="text" class="form-control group-member" style="width:100%;" name="Field1" ng-model="data.myField2"  />
            <input type="text" class="form-control group-member" style="width:100%;" name="Field2" ng-model="data.myField3"  />
        </div>

此处group指令标识逻辑分组。该指令位于没有ng-model的元素上,上例中为div。 group指令会收到2个可选属性min-requiredmax-required。在各个字段上使用group-member类标识组成员。组成员应该有ng-model绑定。由于group指令没有ng-model错误,因此在上述情况下yourForm.$error.CancellationInfoGrpReqd会发出错误。唯一错误键是从group指令所在的元素名称生成的,并附加GrpReqd