StackOverflow_24227903: angular-form-validation-but-not-using-inputs

Illustration of answer to http://stackoverflow.com/questions/24227903/angular-form-validation-but-not-using-inputs.

by ExpertSystem

HTML

<script src="http://code.angularjs.org/1.2.18/angular.min.js"></script>
<div ng-controller="myCtrl">
    <h3>Send a message</h3>
    <hr />
    <form name="myForm" ng-submit="sendMessage()" novalidate>
        <b>Message text:</b> 
        <input type="text" name="msgText" ng-model="message.text"
               placeholder="Enter your message..." required />
        <hr />
        <b>Choose one or more message-types:</b>
        <div>
            Email:
            <input type="text" name="rcpEmail" ng-model="recipient.email"
                   placeholder="Send email to..." required-any="msgType" />
            <br />
            Modile:
            <input type="text" name="rcpMobile" ng-model="recipient.mobile"
                   placeholder="Send SMS to..." required-any="msgType" />
            <br />
            Address:
            <input type="text" name="rcpAddress" ng-model="recipient.address"
                   placeholder="Send card to..." required-any="msgType" />
        </div>
        <hr />
        <b>Choose one or more gift-items:</b>
        <div>
            <input type="checkbox" name="itemFlowers" ng-model="items.flowers"
                   required-any="giftItems" />
            Flowers
            |
            <input type="checkbox" name="itemCake" ng-model="items.cake"
                   required-any="giftItems" />
            Cake
            |
            <input type="checkbox" name="itemBook" ng-model="items.book"
                   required-any="giftItems" />
            Book
        </div>
        <hr />
        <button type="submit" ng-disabled="myForm.$invalid">Send message</button>
    </form>
</div>

JavaScript

var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
    $scope.sendMessage = function () {
        if ($scope.myForm.$invalid) return;
        alert('Message sent !');
    };
});

app.directive('requiredAny', function () {
    // Hash 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]);
                }
            });
            
            // Updates the validity state for the 'required' error-key
            // based on the group's status
            function updateValidity() {
                if...