Componente Vue para Navegación de Nivel Infinito con Árboles y Componentes Recursivos

Este componente de Vue implementa botones de navegación con niveles ilimitados mediante recursión, basado en una estructura de árbol jerárquico.

Propiedades del componente

  • :datos => [Array] Datos con formato id y parentId (ver ejemplo).
  • :seleccionados => [Array] Valores actualmente seleccionados.

Eventos del componente

  • @clic => Se emite al hacer clic en un elemento.

Ejemplo de uso

<tree-nav :datos="listaItems" @clic="manejarClicElemento" :seleccionados="elementosSeleccionados"></tree-nav>

Código de ejemplo

new Vue({
    nombre: 'aplicacion',
    plantilla: `
        <tree-nav :datos="listaItems" @clic="manejarClicElemento" :seleccionados="elementosSeleccionados"></tree-nav>
    `,
    componentes: { treeNav },
    datos() {
        return {
            listaItems: [
                { id: 1, nombre: "Región Norte", parentId: 0 },
                { id: 2, nombre: "Ciudad A", parentId: 1 },
                { id: 3, nombre: "Ciudad B", parentId: 1 },
                { id: 4, nombre: "Distrito X", parentId: 3 },
                { id: 5, nombre: "Distrito Y", parentId: 3 },
                { id: 6, nombre: "Subdistrito Z", parentId: 4 },
                { id: 7, nombre: "Región Sur", parentId: 0 },
                { id: 8, nombre: "Ciudad C", parentId: 7 },
                { id: 9, nombre: "Ciudad D", parentId: 7 }
            ],
            elementosSeleccionados: []
        };
    },
    metodos: {
        manejarClicElemento(elemento) {
            console.log(elemento);
        }
    }
}).$mount('#contenedor')

Estilos CSS

<estilo>
    #aplicacion {
        posicion: relativa;
    }
    .boton {
        ancho: 100px;
        relleno: 4px 10px;
        borde: 1px solid #ccc;
        radio-borde: 4px;
        alineacion-texto: centro;
    }
    .boton:hover {
        cursor: predeterminado;
    }
    .contenedor-elemento {
        posicion: absoluta;
        superior: 180px;
    }
    .boton.activo {
        color-fondo: #666;
        color: #fff;
    }
</estilo>

Código de plantilla HTML

<html lang="es">
<cabecera>
    <meta charset="UTF-8">
    <meta http-equiv="X-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <titulo>Documento</titulo>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</cabecera>
<cuerpo>
    <div id="contenedor"></div>
</cuerpo>
</html>

Código JavaScript del componnete

const treeNav = {
    nombre: 'tree-nav',
    propiedades: {
        datos: {
            tipo: Array,
            defecto: () => []
        },
        seleccionados: {
            tipo: Array,
            defecto: () => []
        }
    },
    plantilla: `
    <div>
        <h2>{{seleccionados}}</h2>
        <elemento-tree-nav :items="arbol" @clic="clicElemento" :seleccionados="seleccionados" ref="refNav"></elemento-tree-nav>
    </div>
    `,
    componentes: {
        elementoTreeNav: {
            nombre: 'elemento-tree-nav',
            plantilla: `
            <div>
                <div v-for="(item, indice) in items" :key="indice">
                    <p v-show="item.visible" class="boton" 
                        :class="{activo: seleccionados[item.nivel] == item.nombre}"
                        @click="clicElemento(item, indice)">
                        {{ item.nombre }}
                    </p>
                    <div v-if="item.hijos && item.hijos.length">
                        <elemento-tree-nav class="contenedor-elemento"
                            :seleccionados="seleccionados"
                            :class="['contenedor-nivel-' + item.nivel]" 
                            :items="item.hijos">
                        </elemento-tree-nav>
                    </div>
                </div>
            </div>`,
            propiedades: {
                items: {
                    tipo: Array,
                    defecto: () => []
                },
                seleccionados: {
                    tipo: Array,
                    defecto: () => []
                }
            },
            datos() {
                return {};
            },
            metodos: {
                clicElemento(item, indice) {
                    this.ocultarItems(this.items);
                    this.items.forEach(v => v.visible = true);
                    this.procesarClic(item);
                    this.$emit('clic', item);
                },
                procesarClic(item) {
                    item.visible = true;
                    this.$set(this.seleccionados, item.nivel, item.nombre);
                    this.seleccionados.splice(item.nivel + 1);
                    if (item.hijos && item.hijos.length > 0) {
                        item.hijos.forEach((v, k) => {
                            if (k === 0) this.procesarClic(v);
                            v.visible = true;
                        });
                    }
                },
                ocultarItems(arreglo) {
                    arreglo.forEach(v => {
                        v.visible = false;
                        if (v.hijos) this.ocultarItems(v.hijos);
                    });
                }
            }
        }
    },
    datos() {
        return {
            arbol: []
        };
    },
    metodos: {
        clicElemento(item) {
            this.$emit('clic', item);
        },
        construirArbol(elementos) {
            let raiz = [];
            let asignarHijos = (nodoPadre, nivelPadre) => {
                nivelPadre++;
                elementos.forEach(v => {
                    if (v.parentId === nodoPadre.id) {
                        const nuevoNodo = {
                            ...v,
                            visible: nodoPadre.id === 0,
                            hijos: [],
                            nivel: nivelPadre
                        };
                        raiz.push(nuevoNodo);
                        asignarHijos(nuevoNodo, nivelPadre);
                    }
                });
            };
            asignarHijos({ id: 0 }, -1);
            return raiz;
        }
    },
    montado() {
        this.arbol = this.construirArbol(this.datos);
        setTimeout(() => {
            this.$refs.refNav.clicElemento(this.arbol[0]);
        }, 0);
    }
};

Etiquetas: Vue vue-component recursion tree-structure JavaScript

Publicado el 7-3 05:18