Apex · APEX Css · APEX image · javascript

Reacciones Personalizadas en Oracle APEX

En este artículo, aprenderemos cómo agregar un menú de opciones personalizado a cualquier elemento de tu aplicación, como por ejemplo, botones.

Demo

Para lograrlo, utilizaremos JavaScript, HTML y CSS, combinando estos tres lenguajes para crear una interfaz interactiva y estilizada.

#shared-reactions-panel {
    position: absolute;
    background-color: #fff;
    border: 1px solid #ddd;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
    padding: 8px 12px;
    border-radius: 5px;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
    z-index: 10000; /* Valor muy alto para estar por encima de elementos APEX */
    display: flex;
    gap: 8px;
    align-items: center;
    transform-origin: bottom center;
    pointer-events: none; /* Inicialmente no responde a eventos */
}

#shared-reactions-panel.visible {
    opacity: 1;
    visibility: visible;
    pointer-events: auto; /* Ahora sí responde a eventos */
}

.reaction-icon {
    cursor: pointer;
    user-select: none;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out;
}

.reaction-icon:hover {
    transform: scale(1.15);
    background-color: rgba(0, 0, 0, 0.05); /* Fondo sutil al hacer hover */
}

.reaction-emoji {
    font-size: 1.2em;
    line-height: 1;
}

/* Estilos para los botones que pueden tener reacciones */
.showReactions {
    padding: 8px 15px;
    border: 1px solid #ccc;
    border-radius: 5px;
    background-color: #f0f0f0;
    cursor: pointer;
    transition: background-color 0.2s ease-in-out;
    margin-right: 10px;
    position: relative; /* Importante para el posicionamiento correcto */
}

.showReactions:hover {
    background-color: #e0e0e0;
}

/* Estilo para el emoji seleccionado que se muestra en el botón */
.selected-emoji {
    display: inline-block;
    margin-left: 5px;
    vertical-align: middle;
}

/* Para pantallas táctiles/móviles donde hover no funciona bien */
@media (max-width: 768px) {
    .showReactions:active + #shared-reactions-panel,
    .showReactions:focus + #shared-reactions-panel {
        opacity: 1;
        visibility: visible;
        pointer-events: auto;
    }
}
function initReactions() {
    // Función principal que inicializa el panel de reacciones
    function initReactionsPanel() {
        const reactionPanel = document.getElementById('shared-reactions-panel');
        const triggerButtons = document.querySelectorAll('.showReactions');
        const hideDelay = 250; // Tiempo en ms para ocultar el panel
        let currentTriggerButton = null; // Para rastrear el botón activo

        console.log('Script de reacciones inicializado.');

        if (!reactionPanel) {
            console.error("Error: El elemento con ID 'shared-reactions-panel' no se encontró en el DOM.");
            return;
        }

        if (triggerButtons.length === 0) {
            console.warn("Advertencia: No se encontraron elementos con la clase 'showReactions'.");
            return;
        }

        // Función para posicionar el panel respecto al botón
        const positionPanel = (button) => {
            if (!button) return;

            const btnRect = button.getBoundingClientRect();
            const panelWidth = reactionPanel.offsetWidth;
            const panelHeight = reactionPanel.offsetHeight;
            const scrollY = window.scrollY || document.documentElement.scrollTop;
            const scrollX = window.scrollX || document.documentElement.scrollLeft;
            const offset = 5; // Ajuste de separación

            // Calcular posición vertical - intentar mostrar encima primero
            let top, transformOrigin;
            if (btnRect.top - panelHeight - offset > 0) {
                // Hay espacio arriba, mostrar encima
                top = btnRect.top + scrollY - panelHeight - offset;
                transformOrigin = 'bottom center';
            } else {
                // No hay espacio arriba, mostrar debajo
                top = btnRect.bottom + scrollY + offset;
                transformOrigin = 'top center';
            }

            // Calcular posición horizontal (centrado respecto al botón)
            let left = btnRect.left + scrollX + (btnRect.width / 2) - (panelWidth / 2);
            
            // Ajustes para evitar que se salga de la pantalla
            if (left < scrollX + 5) left = scrollX + 5;
            if (left + panelWidth > window.innerWidth - 5) {
                left = window.innerWidth - panelWidth - 5;
            }

            // Aplicar posición
            reactionPanel.style.top = `${top}px`;
            reactionPanel.style.left = `${left}px`;
            reactionPanel.style.transformOrigin = transformOrigin;
        };

        // Mostrar el panel
        const showPanel = (button) => {
            if (!button || button === currentTriggerButton) return;
            
            clearTimeout(reactionPanel.hideTimeout);
            currentTriggerButton = button;

            // En APEX, es mejor mover el panel al final del body para evitar problemas con contenedores
            if (reactionPanel.parentNode !== document.body) {
                document.body.appendChild(reactionPanel);
            }

            positionPanel(button);
            reactionPanel.classList.add('visible');
        };

        // Ocultar el panel
        const hidePanel = () => {
            clearTimeout(reactionPanel.hideTimeout);
            reactionPanel.hideTimeout = setTimeout(() => {
                reactionPanel.classList.remove('visible');
                currentTriggerButton = null;
            }, hideDelay);
        };

        // Configurar eventos para botones trigger
        triggerButtons.forEach(button => {
            button.addEventListener('mouseenter', () => {
                showPanel(button);
            });
            
            button.addEventListener('mouseleave', () => {
                hidePanel();
            });
        });

        // Eventos para el panel de reacciones
        reactionPanel.addEventListener('mouseenter', () => {
            clearTimeout(reactionPanel.hideTimeout);
        });
        
        reactionPanel.addEventListener('mouseleave', () => {
            hidePanel();
        });

        // Manejar eventos de redimensionamiento para reposicionar el panel si está visible
        window.addEventListener('resize', () => {
            if (currentTriggerButton && reactionPanel.classList.contains('visible')) {
                positionPanel(currentTriggerButton);
            }
        });

        // Manejar eventos de scroll para reposicionar o cerrar el panel
        window.addEventListener('scroll', () => {
            if (currentTriggerButton && reactionPanel.classList.contains('visible')) {
                positionPanel(currentTriggerButton);
            }
        });
    }

    // En Oracle APEX, es buena práctica usar esta comprobación
    if (typeof apex !== 'undefined') {
        apex.jQuery(document).on('apexreadyend', initReactionsPanel);
    } else {
        initReactionsPanel();
    }
};

initReactions();


El HTML dependerá de cuándo y cómo deseamos mostrar las reacciones en la interfaz. Como ejemplo, utilizaremos botones personalizados y un reporte de “Departments” para ilustrar cómo interactuar con los datos en Oracle APEX.

<div id="shared-reactions-panel" class="extended-reactions">
    <div class="reaction-icon like" title="Like"><span class="reaction-emoji">👍</span></div>
    <div class="reaction-icon celebrate" title="Celebrate"><span class="reaction-emoji">🎉</span></div>
    <div class="reaction-icon support" title="Support"><span class="reaction-emoji">❤️</span></div>
    <div class="reaction-icon love" title="Love"><span class="reaction-emoji">😍</span></div>
    <div class="reaction-icon insightful" title="Insightful"><span class="reaction-emoji">💡</span></div>
    <div class="reaction-icon curious" title="Curious"><span class="reaction-emoji">🤔</span></div>
</div>


<button class="showReactions">Reaccionar Aquí</button>
<button class="showReactions">Otro Botón</button>

<div style="margin-top: 100px;">
<button class="showReactions">Botón Más Abajo</button>
</div>

El resultado final es:

Leave a comment