Form validation - Required one of many in a group

后端 未结 8 1211
时光取名叫无心
时光取名叫无心 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: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:

    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.

提交回复
热议问题