Home / Widgets / Registro de Widget Personalizado – Lista Dinámica ACF
Duplicate Snippet

Embed Snippet on Your Site

Registro de Widget Personalizado – Lista Dinámica ACF

¿Qué hace?
A diferencia del listado estándar, este widget se alimenta automáticamente de campos repetidores de ACF. Solo tienen que seleccionar el grupo de campos y el sub-campo de texto, y la lista se generará sola.

Finalidad:
La idea es automatizar el contenido dinámico en los loops y plantillas, ahorrándonos tiempo de maquetación manual y asegurando que el diseño sea consistente en todo el sitio. Ya tiene integrados todos los controles de estilo (colores, hover, alineación flexible y soporte para SVG).

Code Preview
php
<?php
/**
 * Registro de Widget Personalizado - Lista Dinámica ACF
 * Este widget extrae datos de campos repetidores de ACF y permite un diseño profesional.
 */
add_action( 'elementor/widgets/register', function( $widgets_manager ) {
    class Mi_Widget_Lista_Dinamica extends \Elementor\Widget_Base {
        /**
         * Configuración básica del widget: nombre interno, título visible, icono y categoría.
         */
        public function get_name() { return 'lista_dinamica_custom'; }
        public function get_title() { return 'Lista Dinámica ACF'; }
        public function get_icon() { return 'eicon-bullet-list'; }
        public function get_categories() { return [ 'general' ]; }
        /**
         * Obtiene todos los grupos de campos de ACF y filtra solo los de tipo 'repeater'.
         * Se usa para llenar el primer selector de configuración.
         */
        protected function get_acf_repeater_fields() {
            $options = [ '' => 'Seleccione un repetidor...' ];
            if ( ! function_exists( 'acf_get_field_groups' ) ) return $options;
            $field_groups = acf_get_field_groups();
            foreach ( $field_groups as $group ) {
                // Obtenemos los campos de cada grupo usando su ID o clave
                $fields = acf_get_fields( $group['ID'] ?: $group['key'] );
                if ( $fields ) {
                    foreach ( $fields as $field ) {
                        if ( $field['type'] === 'repeater' ) {
                            $options[ $field['name'] ] = $group['title'] . ': ' . $field['label'];
                        }
                    }
                }
            }
            return $options;
        }
        /**
         * Obtiene los sub-campos contenidos dentro de cualquier repetidor detectado.
         * Permite seleccionar qué sub-campo de texto se mostrará en el bullet.
         */
        protected function get_acf_sub_fields() {
            $sub_options = [ '' => 'Seleccione el sub-campo...' ];
            if ( ! function_exists( 'acf_get_field_groups' ) ) return $sub_options;
            $field_groups = acf_get_field_groups();
            foreach ( $field_groups as $group ) {
                $fields = acf_get_fields( $group['ID'] ?: $group['key'] );
                if ( $fields ) {
                    foreach ( $fields as $field ) {
                        // Verificamos si es repetidor y si tiene sub-campos definidos
                        if ( $field['type'] === 'repeater' && isset($field['sub_fields']) ) {
                            foreach ( $field['sub_fields'] as $sub_field ) {
                                $sub_options[ $sub_field['name'] ] = $field['label'] . ' -> ' . $sub_field['label'];
                            }
                        }
                    }
                }
            }
            return $sub_options;
        }
        /**
         * Definición de todos los controles de la interfaz de usuario en Elementor.
         */
        protected function register_controls() {
            // --- TAB CONTENIDO: Configuración de datos ---
            $this->start_controls_section('section_content', [
                'label' => 'Configuración de Lista ACF',
            ]);
            // Selector del campo repetidor principal
            $this->add_control('acf_repeater_field', [
                'label' => 'Seleccionar Repetidor ACF',
                'type' => \Elementor\Controls_Manager::SELECT,
                'options' => $this->get_acf_repeater_fields(),
                'default' => '',
            ]);
            // Selector del sub-campo (solo visible si se ha seleccionado un repetidor)
            $this->add_control('acf_sub_field', [
                'label' => 'Seleccionar Sub-campo (Texto)',
                'type' => \Elementor\Controls_Manager::SELECT,
                'options' => $this->get_acf_sub_fields(),
                'condition' => [
                    'acf_repeater_field!' => '',
                ],
            ]);
            // Control de icono global para todos los elementos de la lista
            $this->add_control('selected_icon', [
                'label' => 'Icono para los ítems',
                'type' => \Elementor\Controls_Manager::ICONS,
                'default' => [
                    'value' => 'fas fa-check',
                    'library' => 'fa-solid',
                ],
            ]);
            $this->end_controls_section();
            // --- TAB ESTILO: LISTA (Configuración del contenedor) ---
            $this->start_controls_section('section_style_list', [
                'label' => 'Lista',
                'tab' => \Elementor\Controls_Manager::TAB_STYLE,
            ]);
            // Control deslizante para el margen inferior entre elementos
            $this->add_responsive_control('space_between', [
                'label' => 'Espacio intermedio',
                'type' => \Elementor\Controls_Manager::SLIDER,
                'selectors' => [ '{{WRAPPER}} .custom-list-item:not(:last-child)' => 'margin-bottom: {{SIZE}}{{UNIT}};' ],
            ]);
            // Alineación de texto de la lista completa
            $this->add_responsive_control('align', [
                'label' => 'Alineación',
                'type' => \Elementor\Controls_Manager::CHOOSE,
                'options' => [
                    'left'    => [ 'title' => 'Izquierda', 'icon' => 'eicon-text-align-left' ],
                    'center'  => [ 'title' => 'Centro', 'icon' => 'eicon-text-align-center' ],
                    'right'   => [ 'title' => 'Derecha', 'icon' => 'eicon-text-align-right' ],
                ],
                'selectors' => [ '{{WRAPPER}} .custom-list-container' => 'text-align: {{VALUE}};' ],
            ]);
            $this->end_controls_section();
            // --- TAB ESTILO: ICONO (Personalización visual del icono) ---
            $this->start_controls_section('section_style_icon', [
                'label' => 'Icono',
                'tab' => \Elementor\Controls_Manager::TAB_STYLE,
            ]);
            // Sub-pestañas para colores en estado Normal y Hover
            $this->start_controls_tabs('icon_colors_tabs');
            $this->start_controls_tab('icon_normal_tab', [ 'label' => 'Normal' ]);
            $this->add_control('icon_color', [
                'label' => 'Color',
                'type' => \Elementor\Controls_Manager::COLOR,
                'selectors' => [ '{{WRAPPER}} .custom-list-icon i, {{WRAPPER}} .custom-list-icon svg' => 'color: {{VALUE}}; fill: {{VALUE}};' ],
            ]);
            $this->end_controls_tab();
            $this->start_controls_tab('icon_hover_tab', [ 'label' => 'Al pasar el cursor' ]);
            $this->add_control('icon_color_hover', [
                'label' => 'Color',
                'type' => \Elementor\Controls_Manager::COLOR,
                'selectors' => [ '{{WRAPPER}} .custom-list-item:hover .custom-list-icon i' => 'color: {{VALUE}};' ],
            ]);
            $this->end_controls_tab();
            $this->end_controls_tabs();
            // Tamaño del icono (Soporta SVG y fuentes de iconos)
            $this->add_responsive_control('icon_size', [
                'label' => 'Tamaño',
                'type' => \Elementor\Controls_Manager::SLIDER,
                'default' => [ 'size' => 14, 'unit' => 'px' ],
                'selectors' => [ 
                    '{{WRAPPER}} .custom-list-icon i' => 'font-size: {{SIZE}}{{UNIT}};',
                    '{{WRAPPER}} .custom-list-icon svg' => 'width: {{SIZE}}{{UNIT}}; height: auto;' 
                ],
            ]);
            // Espacio entre el icono y el texto (margin-right)
            $this->add_responsive_control('icon_indent', [
                'label' => 'Brecha',
                'type' => \Elementor\Controls_Manager::SLIDER,
                'selectors' => [ '{{WRAPPER}} .custom-list-icon' => 'margin-right: {{SIZE}}{{UNIT}};' ],
            ]);
            // Alineación horizontal usando Flexbox dentro del ítem
            $this->add_responsive_control('icon_horizontal_align', [
                'label' => 'Alineación horizontal',
                'type' => \Elementor\Controls_Manager::CHOOSE,
                'options' => [
                    'flex-start' => [ 'title' => 'Izquierda', 'icon' => 'eicon-h-align-left' ],
                    'center'     => [ 'title' => 'Centro', 'icon' => 'eicon-h-align-center' ],
                    'flex-end'   => [ 'title' => 'Derecha', 'icon' => 'eicon-h-align-right' ],
                ],
                'selectors' => [ '{{WRAPPER}} .custom-list-item' => 'justify-content: {{VALUE}};' ],
            ]);
            // Alineación vertical para centrar icono respecto a textos largos
            $this->add_responsive_control('icon_vertical_align', [
                'label' => 'Alineación vertical',
                'type' => \Elementor\Controls_Manager::CHOOSE,
                'options' => [
                    'flex-start' => [ 'title' => 'Arriba', 'icon' => 'eicon-v-align-top' ],
                    'center'     => [ 'title' => 'Medio', 'icon' => 'eicon-v-align-middle' ],
                    'flex-end'   => [ 'title' => 'Abajo', 'icon' => 'eicon-v-align-bottom' ],
                ],
                'selectors' => [ '{{WRAPPER}} .custom-list-item' => 'align-items: {{VALUE}};' ],
            ]);
            // Ajuste fino de posición mediante transformadas de CSS
            $this->add_responsive_control('vertical_align_adjust', [
                'label' => 'Ajustar la posición vertical',
                'type' => \Elementor\Controls_Manager::SLIDER,
                'range' => [ 'px' => [ 'min' => -50, 'max' => 50 ] ],
                'default' => [ 'size' => 0, 'unit' => 'px' ],
                'selectors' => [ '{{WRAPPER}} .custom-list-icon' => 'transform: translateY({{SIZE}}{{UNIT}});' ],
            ]);
            $this->end_controls_section();
            // --- TAB ESTILO: TEXTO (Tipografía y sombras) ---
            $this->start_controls_section('section_style_text', [
                'label' => 'Texto',
                'tab' => \Elementor\Controls_Manager::TAB_STYLE,
            ]);
            $this->add_group_control(\Elementor\Group_Control_Typography::get_type(), [
                'name' => 'text_typography',
                'selector' => '{{WRAPPER}} .custom-list-text',
            ]);
            $this->add_group_control(\Elementor\Group_Control_Text_Shadow::get_type(), [
                'name' => 'text_shadow',
                'selector' => '{{WRAPPER}} .custom-list-text',
            ]);
            $this->start_controls_tabs('text_colors_tabs');
            $this->start_controls_tab('text_normal_tab', [ 'label' => 'Normal' ]);
            $this->add_control('text_color', [
                'label' => 'Color',
                'type' => \Elementor\Controls_Manager::COLOR,
                'selectors' => [ '{{WRAPPER}} .custom-list-text' => 'color: {{VALUE}};' ],
            ]);
            $this->end_controls_tab();
            $this->start_controls_tab('text_hover_tab', [ 'label' => 'Al pasar el cursor' ]);
            $this->add_control('text_color_hover', [
                'label' => 'Color',
                'type' => \Elementor\Controls_Manager::COLOR,
                'selectors' => [ '{{WRAPPER}} .custom-list-item:hover .custom-list-text' => 'color: {{VALUE}};' ],
            ]);
            $this->end_controls_tab();
            $this->end_controls_tabs();
            $this->end_controls_section();
        }
        /**
         * Renderizado en el Frontend.
         * Aquí se une la lógica de ACF con la estructura HTML y las clases CSS de Elementor.
         */
        protected function render() {
            $settings = $this->get_settings_for_display();
            $repeater_name = $settings['acf_repeater_field'];
            $sub_field_name = $settings['acf_sub_field'];
            // Verificación de seguridad: si no hay repetidor o ACF no está activo, detenemos la ejecución
            if ( empty( $repeater_name ) || ! function_exists('get_field') ) return;
            // Extraemos las filas del repetidor del post actual
            $rows = get_field( $repeater_name );
            if ( $rows ) {
                echo '<div class="custom-list-container">';
                foreach ( $rows as $row ) {
                    // Obtenemos el valor del sub-campo seleccionado para cada fila
                    $text = isset( $row[$sub_field_name] ) ? $row[$sub_field_name] : '';
                    
                    echo '<div class="custom-list-item" style="display: flex;">';
                    
                    // Renderizado del icono seleccionado
                    if ( ! empty( $settings['selected_icon']['value'] ) ) {
                        echo '<span class="custom-list-icon" style="display: inline-flex; line-height: 1;">';
                        \Elementor\Icons_Manager::render_icon( $settings['selected_icon'], [ 'aria-hidden' => 'true' ] );
                        echo '</span>';
                    }
                    
                    // Renderizado del texto (limpio de etiquetas maliciosas con esc_html)
                    echo '<span class="custom-list-text">' . esc_html($text) . '</span>';
                    echo '</div>';
                }
                echo '</div>';
            }
        }
    }
    /**
     * Finalmente registramos la clase creada en el gestor de widgets de Elementor.
     */
    $widgets_manager->register( new Mi_Widget_Lista_Dinamica() );
});

Comments

Add a Comment