Form validation - Required one of many in a group

后端 未结 8 1203
时光取名叫无心
时光取名叫无心 2020-12-05 18:31

In the project I\'m working on at the moment I currently have three textboxes and I need to validate that at least one of the text boxes has been populated.

I\'ve be

相关标签:
8条回答
  • 2020-12-05 19:16

    You can add required attribute for each of them , and at the end , you can rely your validation on each/all/or just one of them

            <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>
    

    This way if just one of them is filled , button will be enable

    I don't know how you're gonna show that form is not valid , but I think desabling the submit button is the general way

    0 讨论(0)
  • 2020-12-05 19:17

    It's too late but might be can save some one's time:

    If there are only two fields, and want to make one of them required then

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

    If you have three like in question then

    <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)" />
    

    If more than this, I will suggest to write a function on scope and watch it.

    See the working example

    0 讨论(0)
  • 2020-12-05 19:17

    Here is a refactored take on ExpertSystems great post. I didn't need the destroy method so I gutted it.

    I also added a grayed out explanation that may help in your code. I use this directive for ALL my required fields. Meaning when I use this directive I no longer use ng-required, or required.

    If you want a field required just pass in a unique group name. If you don't want the field required then pass in null, and if you want to have many different groups just pass in a matching group name.

    I believe there is a little more refactoring that could be done here. Angularjs states that when using $setValidity, that instead you should use $validators pipeline instead, but I could not get that to work. I am still learning this complex animal. If you have more info, post it!

    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.
    
    0 讨论(0)
  • 2020-12-05 19:24

    modification to ExpertSystem's answer (https://stackoverflow.com/a/24230876/4968547) so that his code works in the latest angularjs.

    i changed the updateValidity() to set parse also to true/false

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

    now its working fine for me

    0 讨论(0)
  • 2020-12-05 19:25

    Ran into this same problem last week; ExpertSystem's solution was a good start, but I was looking for a few enhancements to it:

    • Use Angular 1.4.3
    • Use ngMessages

    I eventually wound up with this example on JSFiddle - hope that helps inspire others in the same boat! Relevant JS code from the Fiddle:

    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;
                };
            }
        };
    });
    
    0 讨论(0)
  • 2020-12-05 19:26

    There are several approaches and the best option depends on your exact requirements.

    Here is one approach that I found to be generic enough and flexible.
    By "generic" I mean it doesn't only work for text-fields, but also for other kinds of inputs, such as check-boxes.
    It's "flexible" because it allows any number of control-groups, such that at least one control of each group must be non-empty. Additionally, there is no "spacial" constraint - the controls of each group can be anywhere inside the DOM (if required, it is easy to constrain them inside a single form).

    The approach is based on defining a custom directive (requiredAny), similar to ngRequired, but taking into account the other controls in the same group. Once defined, the directive can be used like this:

    <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>
    

    In the above example, at least one of [inp1, inp2, inp3] must be non-empty, because they belong to group1.
    The same holds for [inp4, inp5], which belong to group2.


    The directive looks like this:

    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);
        }
      };
    });
    

    This might not be so short, but once included into a module, it is very easy to integrate into your forms.


    See, also, this (not so) short demo.

    0 讨论(0)
提交回复
热议问题