Search code examples
angularjsvalidationangularjs-directiveangular-ngmodel

AngularJS - set validity of wrapped directives


I'm working on a simple directive - date/time picker. It wraps angular material datepicker and two additional selects (for hours and minute), builds the date and exposes it via ngModel.

I have implemented validation logic, where user can supply min and max value, and if the selected date and time falls outside range, model validity is set to false.

Here is the markup of the directive and it's code:

<div layout="column">    
    <div layout="row">
        <md-datepicker name="selectedDatePicker" ng-model="selectedDate" flex="50" flex-order="1"></md-datepicker>
        <md-input-container flex="25" flex-order="2">
            <label>Hour</label>
            <md-select ng-model="selectedHour">
                <md-option ng-repeat="hour in hours" value="{{hour}}">
                    {{hour}}
                </md-option>
            </md-select>
        </md-input-container>
        <md-input-container flex="25" flex-order="2">
            <label>Minute</label>
            <md-select ng-model="selectedMinute">
                <md-option ng-repeat="minute in minutes" value="{{minute}}">
                    {{minute}}
                </md-option>
            </md-select>
        </md-input-container>
    </div>
    <div layout="row">
        <ng-transclude></ng-transclude>
    </div>
</div>

angular.module('myApp')
    .directive('jcdatetimepicker', function ($parse) {

        var link = function (scope, iElement, iAttrs, ngModel) {

            var initValue = scope.ngModel;
            setDateTime(initValue);

            ngModel.$parsers.push(validateInput);
            ngModel.$formatters.push(validateInput);

            ngModel.$render = function () {
                setDateTime(ngModel.$viewValue);
            }

            scope.$on('jcdatetimepicker:updateModel', function (evt, args) {                
                ngModel.$setViewValue(validateInput(args));
            });

            function validateInput(inputDate){
                var valid = true;                              

                if (angular.isDefined(scope.minValue) && (inputDate < scope.minValue)) {
                    valid = false;
                    ngModel.$setValidity('min', false);
                }
                else {                
                    ngModel.$setValidity('min', true);
                }

                if (angular.isDefined(scope.maxValue) && (inputDate > scope.maxValue)) {
                    valid = false;
                    ngModel.$setValidity('max', false);
                }
                else {
                    ngModel.$setValidity('max', true);
                }
                return valid ? inputDate : undefined;
            }

            function setDateTime(date) {
                if (angular.isDate(date)) {
                    scope.selectedMinute = date.getMinutes();
                    scope.selectedHour = date.getHours();
                    scope.selectedDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
                }                
            }
        };

        return {
            restrict: 'E',
            replace: false,
            transclude: true,
            require: '^ngModel',
            scope: {
                ngModel: '=ngModel',
                minValue: '=min',
                maxValue: '=max'
            },
            controller: 'jcDateTimePickerController',
            link: link,
            templateUrl: 'templates/shared/jcDateTimePicker/jcDatetimePicker.html'            
        }
    }).controller('jcDateTimePickerController', function ($scope, $element) {

        $scope.hours = [];
        $scope.minutes = [];

        $scope.selectedMinute = 0;
        $scope.selectedHour = 12;
        $scope.selectedDate = new Date();
        $scope.isValid = false;
        //initialize hours & minutes
        function init() {
            for (var h = 0; h < 24; h++) {
                $scope.hours.push(h);
            }
            for (var m = 0; m < 60; m++) {
                $scope.minutes.push(m);
            }
        };
        init();

        $scope.$watchGroup(['selectedMinute', 'selectedHour', 'selectedDate'], function (newVals, oldVals, _scope) {
            var minutes = newVals[0];
            var hours = newVals[1];
            var date = newVals[2];

            date.setHours(hours, minutes, 0);
            $scope.$broadcast('jcdatetimepicker:updateModel', date);
        });
    });

It works fine when it comes to validation - ngModel validity of model passed to my directive is set correctly. The problem is, that when the selected value is not valid, I would like also to set validity on the directives that my directive is wrapping mdDatepikcer and mdSelect - so that UI of those directives will also indicate invalid input.

However I have no clue how to grab those wrapped directives, e. g. date picker and tinker with it's validity. In unit tests, I tried with isolated scope, but selectedDatePicker was undefined.

Is that even possible? Or is it possible to get it's ngModel controller, and set validity that way?

Any advice is highly appreciated.


Solution

  • if you have the signature of the scopes of the mdDatepicker and mdSelect, you can use: angular.element($("[name='selectedDatePicker']")).scope, then call the function(s) or set properties that's necessary to give up the behavior you want.

    I haven't tried the code but that's one possible way.