Actions
Padrão #34
openAjustar diretiva de select para permitir desabilitar opções, dependendo da seleção do usuário
Start date:
07/09/2025
Due date:
% Done:
0%
Estimated time:
Files
Updated by jean sodré 6 months ago
- Copied from Padrão #31: Ajustar diretiva Popover Para Funcionar com campos Select added
Updated by jean sodré 6 months ago
- Status changed from a fazer to em execução
Como ficou a diretiva:
.directive('customSelect', function($document, $window, $timeout, $compile) {
return {
restrict: 'E',
scope: {
ngModel: '=',
options: '=',
placeholder: '@',
truncateLength: '@?',
width: '@?',
disabledOptions: '=?',
},
template: `
<div class="custom-select-wrapper"
ng-class="{'active': isOpen}"
ng-style="{'width': width || '100px'}"
data-custom-select="true">
<div class="select-header" ng-click="toggle()" id="{{uniqueId}}" data-custom-select-header="true">
<span>{{ (ngModel || placeholder) | truncate:effectiveTruncate }}</span>
<img src="assets/images/vector.svg" class="arrow-icon" ng-class="{'rotated': isOpen}">
</div>
</div>
`,
link: function(scope, elem) {
scope.isOpen = false;
scope.placeholder = scope.placeholder || 'Selecione a opção';
scope.effectiveTruncate = parseInt(scope.truncateLength, 10) || 6;
scope.uniqueId = 'custom-select-' + Math.random().toString(36).substr(2, 9);
scope.isDisabled = function(option) {
return scope.disabledOptions && scope.disabledOptions.indexOf(option) !== -1;
};
let bodyDropdown = null;
let scrollListeners = [];
function setGlobalCustomSelectState(isOpen) {
if (isOpen) {
document.body.setAttribute('data-custom-select-open', 'true');
document.body.setAttribute('data-active-custom-select-id', scope.uniqueId);
} else {
if (document.body.getAttribute('data-active-custom-select-id') === scope.uniqueId) {
document.body.removeAttribute('data-custom-select-open');
document.body.removeAttribute('data-active-custom-select-id');
}
}
}
function createBodyDropdown() {
if (bodyDropdown) return;
// Criar o elemento do dropdown
bodyDropdown = angular.element(`
<div class="select-options-body"
data-custom-select-dropdown="true"
data-parent-id="${scope.uniqueId}"
style="position: absolute; z-index: 99999; background: white; border: 1px solid #ccc; max-height: 300px; overflow-y: auto; box-shadow: 0 4px 12px rgba(0,0,0,0.15); border-radius: 4px;">
<div class="option"
ng-repeat="opt in options track by $index"
ng-click="choose(opt)"
ng-class="{'selected': ngModel === opt, 'disabled': isDisabled(opt)}"
data-custom-select-option="true"
style="padding: 8px 12px; cursor: pointer; background: white; border-bottom: 1px solid #f0f0f0;"
ng-style="{'background-color': isDisabled(opt) ? '#f8f9fa' : (ngModel === opt ? '#007bff' : 'white'), 'color': isDisabled(opt) ? '#6c757d' : (ngModel === opt ? 'white' : 'black'), 'cursor': isDisabled(opt) ? 'not-allowed' : 'pointer'}"
onmouseover="if (!this.classList.contains('disabled')) { this.style.backgroundColor = this.style.backgroundColor === 'rgb(0, 123, 255)' ? '#007bff' : '#f5f5f5'; }"
onmouseout="if (!this.classList.contains('disabled')) { this.style.backgroundColor = this.style.backgroundColor === 'rgb(0, 123, 255)' ? '#007bff' : 'white'; }">
{{opt}}
</div>
</div>
`);
// Compilar com o scope
const compiled = $compile(bodyDropdown)(scope);
angular.element(document.body).append(compiled);
bodyDropdown = compiled;
}
function positionBodyDropdown() {
if (!bodyDropdown) return;
const headerElement = document.getElementById(scope.uniqueId);
if (!headerElement) return;
const rect = headerElement.getBoundingClientRect();
const viewportHeight = $window.innerHeight;
const viewportWidth = $window.innerWidth;
const dropdownHeight = bodyDropdown[0].offsetHeight || 200;
const dropdownWidth = bodyDropdown[0].offsetWidth || parseInt(scope.width) || 100;
let top = rect.bottom + $window.pageYOffset;
let left = rect.left + $window.pageXOffset;
if (rect.bottom + dropdownHeight > viewportHeight) {
top = rect.top + $window.pageYOffset - dropdownHeight;
}
if (rect.left + dropdownWidth > viewportWidth) {
left = viewportWidth + $window.pageXOffset - dropdownWidth - 10;
}
if (left < $window.pageXOffset) {
left = $window.pageXOffset + 10;
}
bodyDropdown.css({
position: 'absolute',
top: top + 'px',
left: left + 'px',
minWidth: rect.width + 'px',
maxWidth: '300px'
});
}
function removeBodyDropdown() {
if (bodyDropdown) {
bodyDropdown.remove();
bodyDropdown = null;
}
scrollListeners.forEach(listener => {
listener.element.removeEventListener('scroll', listener.handler);
});
scrollListeners = [];
}
function addScrollListeners() {
scrollListeners.forEach(listener => {
listener.element.removeEventListener('scroll', listener.handler);
});
scrollListeners = [];
const windowHandler = function() {
if (scope.isOpen && bodyDropdown) {
positionBodyDropdown();
}
};
$window.addEventListener('scroll', windowHandler, true);
scrollListeners.push({
element: $window,
handler: windowHandler
});
let currentElement = elem[0];
while (currentElement && currentElement !== document.body) {
currentElement = currentElement.parentElement;
if (currentElement) {
const styles = $window.getComputedStyle(currentElement);
if (styles.overflow === 'auto' || styles.overflow === 'scroll' ||
styles.overflowY === 'auto' || styles.overflowY === 'scroll') {
const scrollHandler = function() {
if (scope.isOpen && bodyDropdown) {
positionBodyDropdown();
}
};
currentElement.addEventListener('scroll', scrollHandler);
scrollListeners.push({
element: currentElement,
handler: scrollHandler
});
}
}
}
}
scope.toggle = function() {
scope.isOpen = !scope.isOpen;
setGlobalCustomSelectState(scope.isOpen);
if (scope.isOpen) {
createBodyDropdown();
addScrollListeners();
$timeout(positionBodyDropdown, 0);
} else {
removeBodyDropdown();
}
};
scope.choose = function(opt) {
if (scope.isDisabled(opt)) return;
scope.ngModel = opt;
scope.isOpen = false;
setGlobalCustomSelectState(false);
removeBodyDropdown();
};
function close(e) {
const clickedHeader = elem[0].contains(e.target);
const clickedDropdown = bodyDropdown && bodyDropdown[0].contains(e.target);
if (!clickedHeader && !clickedDropdown) {
scope.$apply(() => {
scope.isOpen = false;
setGlobalCustomSelectState(false);
removeBodyDropdown();
});
}
}
function onResize() {
if (scope.isOpen && bodyDropdown) {
positionBodyDropdown();
}
}
$document.on('click', close);
angular.element($window).on('resize', onResize);
scope.$on('$destroy', () => {
setGlobalCustomSelectState(false);
$document.off('click', close);
angular.element($window).off('resize', onResize);
removeBodyDropdown();
});
scope.$watch('ngModel', v => {
if ((v === undefined || v === '') && scope.options && scope.options.length) {
const validOption = scope.options.find(opt => !scope.isDisabled(opt));
scope.ngModel = validOption || scope.options[0];
}
});
scope.$watch('disabledOptions', function() {
if (scope.ngModel && scope.isDisabled(scope.ngModel)) {
scope.ngModel = '';
}
});
scope.$watchCollection('options', function() {
if (scope.isOpen && bodyDropdown) {
$timeout(positionBodyDropdown, 0);
}
});
}
};
})
Actions
