T
T
Timofey Yatsenko2014-05-07 17:57:11
Angular
Timofey Yatsenko, 2014-05-07 17:57:11

How to correctly extend a directive in AngularJS?

I have a task to make a custom select. With optional multi-select option.
Having started writing, I realized that almost the code for a regular select practically does not intersect with the code for a multiselect. In addition to very basic things, like expanding the list, etc.
To make the code easier to read, it was decided to divide them into 2 directives.
The first directive is basic:
dropdownlist - it implements expanding/collapsing the drop-down list, selecting an element from this list, and saving 1 element to the model.
and a second multiselect directive that overrides the behavior of the first.

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();
            };
        }
    };
});

Usage example:
<!--С мультиселектом-->
<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>

I stuffed almost all the logic into the controller. All methods in $scope. In the child directive, I call this controller and override the methods in the scope. Just like in classic OOP. Everything seems to be working.
But for it to work correctly, you need to inject ngModelController into the controller of the dropdownlist directive. And how to do it is not clear.
And in general, all this smacks of crutches. How do you implement such functionality?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
V
Viktor Suzdalev, 2014-05-19
@Kadmil

It is not necessary to throw higher controllers into the directive; if you need a specific function, and it is different in each multiselect, forward it as a parameter to the scope of the directive; it makes sense to remove all the element selection logic from the directive with a drop-down list, and implement it purely on the side of the "multiselect" directive.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question