Javascript atravesar árbol JSON

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");

// último nodo visitado
var lastObject=null;

/**
* Atraviesa un arbol JSON en postorden
* @param x - el nodo actual
* @param level - sólo para debug, caracter para imprimir delante de los nodos
* @parent - referencia al nodo padre o 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 {
// tipus primitiu
}
}

/**
* Determina si el tipo es array
* @param o - el objeto javascript
* @return true si es un aray
*/

function isArray(o) {
return Object.prototype.toString.call(o) === '[object Array]';
}

function traverseArray(arr, level, parent) {
arr.forEach(function(x) {
traverse(x, level + " ", parent);
});
//console.log(level + "<array>");
}

function traverseObject(obj, level, parent) {
//console.log(level + "<object>");

for (var key in obj) {
if (obj.hasOwnProperty(key)) {
lastObj=parent
traverse(obj[key], level + " ", obj);
}
}

// Acumulamos los permisos en el padre
// usamos _und.union para evitar permisos duplicados
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) {
//logger.debug('mirant si menu', md.descripcion, 'te el permís', md.permiso)
// hacemos la intersección del conjunto de permisos del usuario con los del nodo actual
var interseccio=_und.intersection(md.permiso, permisos)
// si el resultado de la intersección es diferente del conjunto vacío el usuario puede ver este menú
if (_und.size(interseccio)>0) {
//logger.debug('te permis de ', md.permiso)
md.visible=true
if (md.submenus) {
recurseMenus(permisos, md.submenus)
}
} else {
//logger.debug('no te el permis')
md.visible = false;
}
})
}

Referencias

https://www.quora.com/How-do-you-loop-through-a-complex-JSON-tree-of-objects-and-arrays-in-JavaScript

Apuntes de Javascript

Apuntes de Javascript

Traducción/resumen de Common JavaScript “Gotchas”. A mí me ha resultado útil para no terminar con una empanada monumental.

La palabra clave var

var declara una variable y siempre debería usarse puesto que en otro caso la variable se declarará goblamente.

¡Siempre usar var para declarar variables!

¿Porqué hay tantas formas de definir una función? ¿Cuáles son las diferencias?

Encontraremos tres formas comunes de definir una función:

myFunction1 = function(arg1, arg2) {};     // NEVER do this!
function myFunction2(arg1, arg2) {}; // This is OK, but...
var myFunction3 = function(arg1, arg2) {}; // This is best!

La primera define la función y la asigna a una variable globalmente. Tal vez no es lo que deseamos.

La segunda tiene el scope adecuado pero tenemos que tener en cuenta las closures.

La tercera es la mejor ya que es sintácticamente consistente con el punto anterior y no pone la función en el scope global. Incluso podemos mejorarla con

var myFunction5 = function aDifferentName(arg1, arg2) {};

console.log(myFunction5.name); // logs "aDifferentName"

En este caso, name queda definido al nombre de la función lo que puede ser útil para hacer debug y mejora la claridad del código.

La palabra clave this: Cómo se comporta?

Generalmente, no es necesario usar this ya que no se comporta como en el resto de lenguajes OOP. En este sentido, es recomendable seguir el patrón module.

La palabra clave this es un puntero al contexto de ejecución actual. Con un ejemplo se ve mejor:

var myFunction = function()
{

console.log(this);
};
var someObject = {}; // Create an empty object. Same as: new Object();
someObject.myFunction = myFunction; // Give someObject a property
someObject.myFunction(); // Logs Object
myFunction(); // Logs...Window?

La primera llamada logea el objeto someObject porque se ejecuta en el contexto de este. Sin embargo, en la segunda llamada myFunction();se ejecuta desde el contexto de la raiz del navegador y por eso logea el objeto `window’.

Lo importante es recordar que this apunta a lo que sea que haya “a la izquierda del punto”.

Qué demonios es una closure?

Es una característica del lenguaje que permite que una variable no se destruya si su valor podría ser requerido en el futuro. Esto es necesario en el manejo de eventos.

Supongamos este código:

// When someone clicks a button, show a message.
var setup = function()
{

var clickMessage = "Hi there!";
$('button').click
(
function()
{

window.alert(clickMessage);
}
);
};
setup();

En un leguaje sin closures, lo más probable es que la variable clickMessage ya no existiera en el momento en que el usuario pulsara el botón. Pero Javascript “sabe” que debe mantener la variable.

Pero es que aún podemos llegar un poco más lejos con algo como esto:

(function()
{
var clickMessage = "Hi there!";
$('button').click
(
function()
{
window.alert(clickMessage);
}
)
;
})
();

En este ejemplo ni siquiera tenemos que dar nombre a la función. Como quiera que hemos terminado el código con () se ejecutará una vez y ya podemos olvidarnos de él con la certeza de que todo lo declarado dentro existirá en el momento en que el usuario haga click.

Ondas de radio y gráfico distribución espectro electromagnético

Artículo educativo sobre tdt que lleva unos gráficos muy completos sobre el espectro.

Sólo recordar que las ondas, según su trayectoria, pueden ser:


  1. Ondas de cielo (se reflejan en la ionosfera)
  2. Ondas de espacio (se propagan en la troposfera bien sea directamente o bien reflejada en tierra o en cielo). Generalmente necesitamos visibilidad directa.
  3. Ondas de tierra (se propagan cerca de tierra, que es condutora, sólo las que tienen polarización vertical)


Cabe añadir que las ondas en el rango de 0-3MHz (ELF a MF) tienen las características siguientes:

  • Las transmisiones siguen la curvatura del globo (ondas de tierra o superficiales)
  • Se usan en el mar a causa de su conductividad.
  • Las antenas son de grandes dimensiones
  • Las condiciones atmosféricas son un problema para las transmisiones


Para el rango HF, de 3MHz a 30 MHz

  • La transmisión se ve muy afectada por las condiciones climatológicas
  • No se reflejan en la ionosfera

Y desde 300MHz  (UHF ) a 300GHz ( EHF - Microondas)

  • No se reflejan en la ionosfera (sólo troposfera)
  • No hay propagación superficial


Distribución

Detalle

Cita

Sólo los sabios discuten, los demás imponen sus ideas.