Project

General

Profile

Actions

Padrão #34

open

Ajustar diretiva de select para permitir desabilitar opções, dependendo da seleção do usuário

Added by jean sodré 6 months ago. Updated 6 months ago.

Status:
concluído
Priority:
normal
Assignee:
Category:
-
Start date:
07/09/2025
Due date:
% Done:

0%

Estimated time:

Description

Referente a esse pedido, da tarefa #2


Files


Related issues 1 (1 open0 closed)

Copied from Padrão #31: Ajustar diretiva Popover Para Funcionar com campos Selectconcluídojean sodré07/09/2025

Actions
Actions #1

Updated by jean sodré 6 months ago

  • Copied from Padrão #31: Ajustar diretiva Popover Para Funcionar com campos Select added
Actions #2

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 #3

Updated by jean sodré 6 months ago

  • Status changed from em execução to teste
Actions #4

Updated by Henrique Novaes 6 months ago

  • Status changed from teste to concluído
Actions

Also available in: Atom PDF