@thekip
Php/C#/Js Developer

Как правильно расширить директиву в AngularJS?

Есть у меня задача сделать кастомный селект. С опциональной возможностью мультивыбора.
Начав писать, понял что почти код для обычного селекта практически не пересекается с кодом для мультиселекта. Кроме совсем базовых вещей, вроде раскрытия списка и т.п.

Для упрощения чтения кода было принято решение разделить их на 2 директивы.
Первая директива базовая:
dropdownlist - она реализовывает раскрытие/сворачивание выпадающего списка, выбор из этого списка эелемента, и сохранение 1 елемента в модель.

и вторая директива multiselect которая перекрывает поведение первой.

filterApp.directive('dropdownList', function() {
    return {
        restrict: 'E',
        require: '^ngModel',
        scope: {
            items: '=',
            textField: '@',
            valueField: '@',
            ngModel: '='
        },
        templateUrl: filterAppUrl + '/partials/dropdownListTtpl.html',
        controller: function($scope, $element, $attrs) {
            
            this.scope = $scope;

            this.valueField = $scope.valueField.toString().trim();
            this.textField = $scope.textField.toString().trim();
            this.ddmenu = $element.find('.dropdown-menu');
            
            $scope.isOpen = false;
            
            $scope.open = function() {
                $scope.isOpen = true;

                $('body').on('click.ddmenu', function(event) {
                    if ($(event.target).closest(this.ddmenu).length == 0) {
                        $scope.close();
                        $scope.$apply();
                    }
                });
            };

            $scope.close = function() {
                $scope.isOpen = false;
                $('body').off('click.ddmenu');
            };

            $scope.toggle = function($event) {
                $event.stopPropagation();
                $scope.isOpen ? $scope.close() : $scope.open();
            };

            $scope.getItemName = function(item) {
                return item[this.textField];
            };
            
            $scope.isSelected = function(_item) {
                return  typeof ($scope.ngModel) != "undefined" && $scope.ngModel &&
                        _item[this.valueField].toString() == $scope.ngModel.toString();
            };
            
            $scope.setLabel = function() {
                if (typeof ($scope.ngModel) == "undefined" || !$scope.ngModel || $scope.ngModel.length < 1) {
                    $scope.currentItemLabel = $attrs.defaultText;
                } else {
                    $scope.currentItemLabel = $scope.ngModel[this.textField].toString();
                }
            };
             $scope.selectVal = function(_item) {
                 console.log($scope.ngModel);
                if (typeof ($scope.ngModel) != "undefined" && $scope.ngModel) {
                    $scope.ngModel = _item;
                }

                $scope.setLabel();
                //ngModelCtrl.$setViewValue($scope.ngModel);
                $scope.close();
            };

            $scope.setLabel();
        },
        
        link: function($scope, element, attrs, ngModelCtrl) {
            ngModelCtrl.$render = function(){
                $scope.setLabel();
            } ;
            
            
            //loading
            $scope.$watch('items', function(data){
                   $scope.showLoading = (data.$resolved != undefined) & !data.$resolved;
            }, true);
        }
    };
});

filterApp.directive('multiselect', function() {
    return {
        restrict: 'A',
        require: ['dropdownList', '^ngModel'],

        link: function(scope, element, attrs, ctrls) {
            
            var ddListCtrl = ctrls[0];
            var ngModelCtrl = ctrls[1];
            
            var scope = ddListCtrl.scope;
            
            scope.isMultiSelect = true;
           
            var valueField = ddListCtrl.valueField;
            var textField = ddListCtrl.textField;

            scope.selectVal = function(_item) {
                var found = false;
                if (typeof (scope.ngModel) != "undefined" && scope.ngModel) {
                    for (var i = 0; i < scope.ngModel.length; i++) {
                        if (!found) {
                            if (_item[valueField].toString() === scope.ngModel[i][valueField].toString()) {
                                found = true;
                                var index = scope.ngModel.indexOf(scope.ngModel[i]);
                                scope.ngModel.splice(index, 1);
                            }
                        }
                    }
                } else {
                    scope.ngModel = [];
                }
                if (!found) {
                    scope.ngModel.push(_item);
                }

                scope.setLabel();
                ngModelCtrl.$setViewValue(scope.ngModel);
            };
            
            scope.setLabel = function() {
                if (typeof (scope.ngModel) =="undefined" || !scope.ngModel || scope.ngModel.length < 1) {

                        scope.currentItemLabel = attrs.defaultText;

                } else {
                    var allItemsString = '';
                    var selectedItemsCount = scope.ngModel.length;
                    if (selectedItemsCount < 3) {
                        var itemsStrings = [];
                        angular.forEach(scope.ngModel, function(item) {
                            itemsStrings.push(item[textField].toString())
                        });
                        allItemsString = itemsStrings.join(" ,");
                    } else {
                        allItemsString = selectedItemsCount + " выбрано";
                    }
                    scope.currentItemLabel = allItemsString;
                }
            };
            
            scope.setLabel();

            scope.isSelected = function(_item) {
                var found = false;
                angular.forEach(scope.ngModel, function(item) {
                    if (!found) {
                        if (_item[valueField].toString() === item[valueField].toString()) {
                            found = true;
                        }
                    }
                });
                return found;
            };

            scope.cancelClose = function($event) {
                $event.stopPropagation();
            };
        }
    };
});


Пример использования:
<!--С мультиселектом-->
<dropdown-list multiselect ng-model="nights" items="nightsItems" text-field="Name" default-text="все" value-field="name" ></dropdown-list>

<!--Без-->
 <dropdown-list ng-model="departureCities" items="departureCityItems" default-text="все" text-field="Name"  value-field="name" ></dropdown-list>


Запихнул почти всю логику в контроллер. Все методы в $scope. В дочерней директиве вызываю этот контроллер и перекрываю методы в скопе. Прям как в классическом ООП. Вроде все работает.

Но для правильно работы нужно внедрить ngModelController в контроллер директивы dropdownlist. А как это сделать непонятно.

Да и вообще все это попахивает костылями. Как вы реализовываете подобный функционал?
  • Вопрос задан
  • 3115 просмотров
Пригласить эксперта
Ответы на вопрос 1
Kadmil
@Kadmil
эпикуреец-консультант
Не надо контроллеры вышестоящие в директиву кидать; если нужна конкретная функция, и она разная в каждом мультиселекте - пробрасывайте параметром в scope директивы; есть смысл убрать из директивы с выпадающим списком всю логику выбора элемента, и реализовать ее вчистую на стороне директивы "мультиселект".
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы