El problema
Tenemos el árbol de menú de una aplicación. Cada hoja tiene unos permisos asociados. Sólo queremos mostrar los nodos de los que el usuario tenga algún permiso.
Una posible forma de hacerlo es recorrer el árbol una vez y acumular en los nodos los permisos del nivel por debajo. El recorrido sería en postorden.
Los usuarios tienen grupos de permisos (roles). En el momento de generar el menú de un usuario particular, deberemos volver a recorrer el árbol, esta vez en anchura (por niveles), tomando sólo aquellos nodos que tengan algún permiso coincidente. Si no hay ninguno, no es necesario bajar al siguiente nivel de esa rama.
Árbol de menús
var modulos = [
{permiso: [], descripcion: 'Almacenes', url: '#',
submenus: [
{permiso: ['compras.alb.ver'], descripcion: 'Artículos', url: '/app/articulos'},
{permiso: ['compras.prov.ver'], descripcion: 'Proveedores', url: '/app/proveedores'},
{permiso: ['alm.inv.ver'], descripcion: 'Inventario', url: '/app/inventario'},
{permiso: ['alm.edit'], descripcion: 'Almacenes', url: '/app/almacenes'}
]
},
{permiso: [], descripcion: 'Compras', url: '#',
submenus: [
{permiso: ['compras.pedidos.ver'], descripcion: 'Pedidos', url: '/app/compras/pedidos'},
{permiso: ['compras.albaranes.ver'], descripcion: 'Albaranes', url: '/app/compras/albaranes',
submenus: [
{permiso: ['compras.alb.edt'], descripcion: 'Editar', url: '/app/albaranes/edit'},
{permiso: ['compras.alb.lst'], descripcion: 'Listar', url: '/app/albaranes/list'},
]},
]
},
];
El nodo Almacenes debería contener los permisos de los que tiene por debajo, el nodo Albaranes debería contener [compras.alb.edt, compras.alb.lst] y el nodo Compras [compras.pedidos.ver, compras.albaranes.ver, compras.alb.edt, compras.alb.lst].
Recorrido de un árbol JSON en postorden
Observamos que tenemos tanto objects como arrays: Atravesamos los arrays con forEach y los objetos examinando sus claves con for (var key in obj) . Bajamos hasta la primera hoja de la izquierda, visitando las hermanas para subir después al nodo superior. El proceso se repite de forma recursiva hasta haber visitado todos los nodos. Mantenemos la referencia al nodo inmediatamente superior con parent para poder acumular en él los permisos.
var _und = require("underscore");
var lastObject=null;
function traverse(x, level, parent) {
if (isArray(x)) {
traverseArray(x, level, parent);
} else if ((typeof x === 'object') && (x !== null)) {
traverseObject(x, level, parent);
} else {
}
}
function isArray(o) {
return Object.prototype.toString.call(o) === '[object Array]';
}
function traverseArray(arr, level, parent) {
arr.forEach(function(x) {
traverse(x, level + " ", parent);
});
}
function traverseObject(obj, level, parent) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
lastObj=parent
traverse(obj[key], level + " ", obj);
}
}
if (obj && obj.descripcion) {
var p=''
if (parent) {
p=parent.descripcion
parent.permiso=_und.union(parent.permiso, obj.permiso)
} else {
p='/'
}
console.log('visitant', obj.descripcion,'amb pare', p);
}
}
Ahora ya sólo queda hacer
traverse(modulos, '')
Para mostrar los menús, podemos poner una marca de visibilidad y enviar el árbol al renderizador de plantillas que sólo mostrará los que tengan visible==true.
var recurseMenus= function(permisos, m) {
_und.each(m, function(md) {
var interseccio=_und.intersection(md.permiso, permisos)
if (_und.size(interseccio)>0) {
md.visible=true
if (md.submenus) {
recurseMenus(permisos, md.submenus)
}
} else {
md.visible = false;
}
})
}
Referencias
https://www.quora.com/How-do-you-loop-through-a-complex-JSON-tree-of-objects-and-arrays-in-JavaScript