Есть у меня задача сделать кастомный селект. С опциональной возможностью мультивыбора.
Начав писать, понял что почти код для обычного селекта практически не пересекается с кодом для мультиселекта. Кроме совсем базовых вещей, вроде раскрытия списка и т.п.
Для упрощения чтения кода было принято решение разделить их на 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. А как это сделать непонятно.
Да и вообще все это попахивает костылями. Как вы реализовываете подобный функционал?