1

I am setting up custom template for md-select using custom directive but form validiation and required validiation is not working. This is for refactoring template of md-select using custom directive as md-custom-select.

I have created template of md-select in custom directive passing models and lists for dropdown and also required attr. The main problem for this custom directive is , it doesnot validiate form.

I am converting :


    <md-select  required ng-model="vm.Search.ParentDivisionID" ng-change="vm.GetDivisionIDList()">
                                            <md-option ng-value="item.ID" ng-repeat="item in vm.ParentDivisionList">
                                                {{item.Name}}
                                            </md-option>
                                        </md-select>

TO:

<md-custom-select ng-model="vm.Search.ParentDivisionID" list="vm.ParentDivisionList" ng-required="true" ng-change="vm.GetDivisionIDList(vm.Search.ParentDivisionID)"></md-custom-select>


     (function () {
        'use strict';
        window.app.directive('mdCustomSelect', mdCustomSelect);
        mdCustomSelect.$inject = ['$compile'];
        function mdCustomSelect($compile) {
            var styleTemplate = "<style>\
            md-checkbox#checkAllBox{\
                margin: 16px 0px 10px 5px;\
            }\
            md-checkbox#checkAllBox .md-label {\
                margin-bottom: 0px !important;\
            }\
            md-checkbox#checkAllBox .md-label span {\
                margin-left: 3px !important;\
            }\
            md-checkbox#checkAllBox .md-icon {\
                height: 20px !important;\
                width: 20px !important;\
            }\
            .parentLabel .parentLabel label {\
                border-bottom: 1px solid #ccc;\
                color: #000;\
                padding-bottom: 6px;\
            }\
            md-checkbox#checkAllBox:not([disabled]).md-warn.md-checked .md-icon {\
                background-color: rgb(88, 104, 191) !important;\
            }\
            md-checkbox#checkAllBox.md-checked .md-icon:after {\
                height: 13px !important;\
                left: 5px !important;\
            }\
            .parentLabel .md-container-ignore {\
                padding: 0px;\
                margin: 0px;\
                border:none;\
            }\
            </style>";

            var selectAllTemplate = '<div style="padding: 0px 0px 15px 5px; background-color: #efefef; border-bottom: 1px solid #ccc;">\
                <md-checkbox aria-label="Parent Checkbox" class="md-warn" id="checkAllBox" title="Select All" ng-model="checkAllChecked" ng-change="toggleSelectAll()">Check/Uncheck All </md-checkbox>\
            </div>';
            function searchTemplate(placeholder) {
                if (placeholder == undefined || placeholder == "") placeholder = "Search ...";
                return '<md-select-header aria-label="Select Header" class="demo-select-header">\
                <input aria-label="InputSearchBox" ng-keydown="$event.stopPropagation()" ng-model="searchTerm" type="search" placeholder="' + placeholder + '"\
                       class="demo-header-searchbox md-text">\
            </md-select-header>';
            }
            function getOptionTemplate(isMultiple, isGroup) {
                var optionTemplate = '<md-option aria-label="Dropdown Selector" ng-value="subItem.ItemID" ng-repeat="subItem in ' + (isGroup ? 'item.SubItems' : 'ItemList') + ' | filter: searchTerm">{{subItem.ItemName}}</md-option>';
                if (isGroup == false) {
                    return (isMultiple ? selectAllTemplate : '') + optionTemplate;
                }
                return (isMultiple ? selectAllTemplate : '') + '<md-optgroup aria-label="Dropdown Selector" class="parentLabel" ng-repeat="item in ItemList">\
                    <div ' + (isMultiple ? 'ng-click="item.toggleSelect() "' : '') + 'style="padding:10px 10px 10px 10px; font-size:1.4em; color:black; border-bottom: 1px solid #ccc;">\
                        {{item.ItemName}}\
                    </div>'
                    + optionTemplate +
                '</md-optgroup>';
            }
            function getMdSelectOptionsTemplate($scope) {
                var returnTemplate = $scope.isMultipleSelected ? 'multiple ' : '';
                returnTemplate += 'ng-required="isRequired" ';
                returnTemplate +='ng-disabled="isDisabled" ';
                return returnTemplate;
            }
            var linker = function ($scope, element, attrs, controllers) {
                //https://github.com/angular/angular/issues/10094 Binding required attribute always applies the required validator
                $scope.isMultipleSelected = "multiple" in attrs ? (attrs.multiple == "false" ? false : true) : false;
                $scope.isGroup = "group" in attrs ? (attrs.group == "false" ? false : true) : false;
                $scope.ngModelCtrl = controllers.ngModelCtrl;
                var ngModelName = attrs.ngModel;

                var completeTemplate = "";
                completeTemplate += styleTemplate;
                //1 begin
                completeTemplate += '<md-select ' + getMdSelectOptionsTemplate($scope) + 'ng-model="ngModel" md-on-open="mdOpen()" md-on-close="mdClose()" ng-change="valChanged()" data-md-container-class="selectdemoSelectHeader" class="md-no-asterisk">';

                completeTemplate += searchTemplate(attrs.placeholder);//2 begin and end
                completeTemplate += getOptionTemplate($scope.isMultipleSelected, $scope.isGroup); // 3 begin and end
                completeTemplate += ' </md-select>';//1 end

                element.html(completeTemplate);
                $compile(element.contents())($scope);
            };

            return {
                restrict: "E",
                require: {
                    ngModelCtrl: '^ngModel'
                },
                scope: { // also uses multiple and group attribute
                    list: "=",
                    ngModel: "<",
                    mdOnClose: "&",
                    ngOption: "<",
                    mdChangeOnClose: "&", // event fire when selector is closes with changed value
                    isDisabled: "=ngDisabled",
                    isRequired: "=ngRequired",
                    placeholder: "@"
                },
                replace: true,
                link: linker, // link is called after controller
                controller: ['$scope', function ($scope) {
                    var defaultValueProperty = $scope.ngOption == undefined || $scope.ngOption.Value == undefined ? 'DivisionID' : $scope.ngOption.Value;
                    var defaultTextProperty = $scope.ngOption == undefined || $scope.ngOption.Text == undefined ? 'DivisionName' : $scope.ngOption.Text;
                    var defaultParentValueProperty = $scope.ngOption == undefined || $scope.ngOption.ParentValue == undefined ? 'ParentDivisionID' : $scope.ngOption.ParentValue;
                    var defaultParentTextProperty = $scope.ngOption == undefined || $scope.ngOption.ParentText == undefined ? 'ParentDivisionName' : $scope.ngOption.ParentText;

                    $scope.ItemList = [];
                    var rawItemList;
                    $scope.$watch('list', function (newValue, OldValue) {
                        formatListForView(newValue);
                    }, true);
                    $scope.toggleSelectAll = function () {
                        valueChanged = true;
                        if ($scope.checkAllChecked == false) {
                            $scope.ngModelCtrl.$setViewValue([]);
                        } else {
                            $scope.ngModelCtrl.$setViewValue(rawItemList.map(function (item) { return item[defaultValueProperty]; }));
                        }
                    };

                    function formatListForView(rawList) {
                        rawList = rawList == null || rawList == undefined ? [] : rawList;
                        rawItemList = rawList;
                        var parentList = [];
                        var idList = [];
                        // if no grouping then just return the modified itemlist
                        if ($scope.isGroup == false) {
                            $scope.ItemList = rawList.map(function (subItem) {
                                return {
                                    ItemID: subItem[defaultValueProperty],
                                    ItemName: subItem[defaultTextProperty]
                                }
                            });
                            return; // below code is for grouping, not needed for group==false
                        }
                        // fill parent
                        rawList.forEach(function (item, index) {
                            if ((item[defaultParentValueProperty] != null || item[defaultParentValueProperty] != 0) && idList.indexOf(item[defaultParentValueProperty]) == -1) {
                                idList.push(item[defaultParentValueProperty]);
                                parentList.push({
                                    ItemID: item[defaultParentValueProperty],
                                    ItemName: item[defaultParentTextProperty]
                                });
                            }
                        });
                        // fill children
                        parentList.forEach(function (item, index) {
                            item.SubItems = rawList.filter(function (subitem) {
                                return subitem[defaultParentValueProperty] == item.ItemID;
                            }).map(function (subItem) {
                                return {
                                    ItemID: subItem[defaultValueProperty],
                                    ItemName: subItem[defaultTextProperty]
                                }
                            });
                            // toggle children from parent
                            item.toggleSelect = function () {
                                var valuesToToggle = item.SubItems.map(function (item) {
                                    return item.ItemID;
                                });
                                valueChanged = true;
                                var alreadySelected = _.intersection(valuesToToggle, $scope.ngModel);
                                if (alreadySelected.length == 0 || (alreadySelected.length > 0 && alreadySelected.length != valuesToToggle.length)) { // select all from the parent
                                    $scope.ngModelCtrl.$setViewValue(_.union(valuesToToggle, $scope.ngModel));
                                } else { // select none from the parent
                                    $scope.ngModelCtrl.$setViewValue(_.difference($scope.ngModel, valuesToToggle));
                                }
                            }
                        });
                        $scope.ItemList = parentList;
                    }
                    var valueChanged = false; // check if value changed for ngChangeOnClose
                    $scope.valChanged = function () {
                        valueChanged = true;
                        // for ng-change
                        $scope.ngModelCtrl.$setViewValue($scope.ngModel);
                    }
                    $scope.mdOpen = function () {
                        valueChanged = false;
                    }
                    $scope.mdClose = function () {
                        if (typeof $scope.mdOnClose === 'function') $scope.mdOnClose();
                        if (valueChanged) {
                            if (typeof $scope.mdChangeOnClose === 'function') $scope.mdChangeOnClose();
                        }
                    }
                }]
            }
        }
    })(); 

My expected result from this is it should work for form validiation.

1
  • Without actually seeing it up and running, but comparing it to one of my own uibTypeahead wrappers I think the main issue is the scope.ngModel being < instead of = which I think is breaking the validation from the higher level. For my wrapper I don't pass through things like required, validate, ui-validate I let them link to the ngModelController at the top level. Any custom validation I implement using another directive then do an ngModel.$validators.member = .... (to validate a member object for example). Commented Sep 5, 2019 at 11:57

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.