Reference Source Test

RxMap

Build Status Codacy Badge Coverage Status dependencies Status devDependencies Status Code style: airbnb AUR

  

RxMap es una "Wrapper" para las librerías de mapas que añade programación funcional y reactiva usando observables(RxJs) , permite fácilmente anidar llamadas asyncronas , permite disponer de diferentes libreras de mapas con la misma interfaz y incluye la opcion de trabajar offline.

RxMap no sustituye al visor sino que añade a los visores actuales una capa adicional para poder mejorar el mantenimiento de tus proyectos. Y añade funcionalidades adicionales para mejorar la performance y la escalabilidad de tu código.

RxMap se basa en tres conceptos acciones , observadores y funciones para organizar tu código en pequeños bloques. Y también te permite escribir middlewares para desacoplar mejor tu código.

De facto incluye observables sobre las acciones y un store obsevable, para poder observar los cambios sobre datos que guardes.

RxMap añade una capa de abstracción sobre las librerías de mapas, esta diseñada para desacoplarte y evitarte tener que actualizar tu código con cada cambio de librerías o utilizar una mapa diferente según el entorno. Podrás centrarte en desarrollar tus funcionalidades.

RxMap te permite cargar el código en diferido cuando la utilizas para mejorar los tiempos de carga, es recomendable desplegarlo en http2.

Ventajas

  • Cargar en diferido de las acciones y los observadores (Lazy Loader).
  • Trabajar con observadores. (RxJs).
  • Trabajar de manara asyncrona.
  • Programación funcional.
  • Store Observable.
  • Trabaja con multiples mapas, sin cambiar tu código.
  • Reutilización de código entre diferentes proyectos.
  • Offline

Instalación

NPM

Npm install @rxmap\rxmap —save

CDN

<head>
<script type=‘application/javascript’ src=‘https://unpkg.com/@rxmap/rxmap@0.5.0’ defer/>
</head>

Como utilizarla

Lo ideal es tener las acciones y los observadores en librerías externas, lo primero seria registrar estas librerías para poder utilizarlas

import rxLib from '@rxmap/basiclib';
registerLib(...rxLib);

Una vez registradas las librerías o las acciones a utilizar. Lo primero es cargar el mapa que se quiera utilizar Y después ya se pueden invocar las acciones o observadores a utilizar.

import { RxMap } from ‘@rxmap/rxmap’;
await RxMap.load(‘leaflet’);
RxMap.create(‘mapId’,2.45,41.56,10);
RxMap.marker(2.45,41.56).popup(‘hello’);

O para versión de CDN.

R.RxMap.load(‘leaflet’);
R.RxMap.create(‘mapId’,2.45,41.56,10);
R.RxMap.marker(2.45,41.56).popup(‘hello’);

La primera acción que se tiene que invocar debe devolver el mapa inicial inicializado, normalmente esta acción se llama ‘create’ y debe ser sincrona, es decir no devolver una promesa. Es recomendable no anidar las llamadas a create, sino volver a hacer llamadas sobre RxMap, como se observa en los ejemplos anteriores.

Documentación

Puedes encontrar más Documentación aquí:

Examples

Librerias

Librerías para añadir acciones y observadores:

Para añadir una nueva librería enviar un mail a rxmap@xlab.tech o realiza un pullRequest de la documentación con la libreria añadida. Para hacer un PullRequest:

  • Haz un fork del repositorio.
  • Haz commit y push de los cambios en tu reposition.
  • Crear un PullRequest .

Mapas Soportados

Offline

El offline funciona a través de un serviceWorker que se encarga de cachear todas las peticiones para que posteriormente puedas acceder a ellas sin cobertura.

Para poder trabajar offline hay que incluir el fichero sw.js en la misma ruta donde esta RxMap. El fichero sw.js debe incluir esta linea

importScripts('https://unpkg.com/@rxmap/offlinestorage@0.4.0/dist/esm/offline-sw.js')
`

para poder activar al serviceWorker que se encargara de cachear las peticiones.

Overview

RxMap es una librería de mapas para programación funcional y reactiva usando observables(RxJs) Y que permite fácilmente anidar llamadas asyncronas.

RxMap se basa en dos conceptos acciones y observadores para organizar tu código en pequeños bloques. Y también te permite escribir middlewares para desacoplar mejor tu código.

De facto incluye observables sobre las acciones y un store obsevable, para poder observar los cambios sobre datos que guardes.

RxMap añade una capa de abstracción sobre las librerías de mapas, esta diseñada para desacoplarte i evitarte tener que actualizar tu código con cada cambio de librerías o utilizar una mapa diferente según el entorno. Podrás centrarte en desarrollar tus funcionalidades.

RxMap te permite cargar el código en diferido cuando la utilizas para mejorar los tiempos de carga, es recomendable desplegarlo en http2.

Ventajas

  • Cargar en diferido de las acciones y los observadores (Lazy Loader).
  • Trabajar con observadores. (RxJs).
  • Trabajar de manara asyncrona.
  • Programación funcional.
  • Store Observable.
  • Trabaja con multiples mapas, sin cambiar tu código.
  • Reutilización de código entre diferentes proyectos.

Como utilizarla

import { RxMap } from ‘@rxmap/rxmap’;
await RxMap.load(‘leaflet’);
RxMap.create(‘mapId’,2.45,41.56,10);
RxMap.marker(2.45,41.56).popup(‘hello’);

O para versión de CDN.

R.RxMap.load(‘leaflet’);
R.RxMap.create(‘mapId’,2.45,41.56,10);
R.RxMap.marker(2.45,41.56).popup(‘hello’);

La primera acción que se tiene que invocar debe devolver el mapa inicial inicializado, normalmente esta acción se llama ‘create’ y debe ser sincrona, es decir no devolver una promesa. Es recomendable no anidar las llamadas a create, sino volver a hacer llamadas sobre RxMap, como se observa en los ejemplos anteriores.

Como funciona

Cargar

Para empezar a utilizar la librería lo primero es cargar el mapa que se quiere utilizar para ello se puede hacer de dos maneras. Directamente con el método load o con loadConfig los dos son métodos asíncronos que puedes usar con await o con formato promesa.

import { RxMap } from '@rxmap/rxmap';
const config = {};
await RxMap.load(‘leaflet’,config);
import { RxMap } from '@rxmap/rxmap';
const config = {};
RxMap.load(‘leaflet’,config).then((map)=>{
   // execute actions
});

O

import { RxMapFromConfig } from '@rxmap/rxmap';

const config = {
  type: 'leaflet',
  options: {
    key: '',
  },
  map: {
    autoCenter: false,
    center: {
      lat: 51.505,
      lng: -0.09,
    },
    zoom: 13,
  },
};


const map = await RxMapFromConfig(config);


Librerías

Para utilizar las acciones y los observadores tienes que cargar las librerías que quieras de acciones y observadores, propias o de terceros. Al cargar las librerías ya tienes disponibles todas las acciones que incorporen estas librerías en RxMap o en los observadores, (‘una vez los recuperas desde RxMap’ < rxMap.observer(from(5))>). Pero al tener carga dinámica no bajas el código de todas las acciones, reduciendo el tiempo de carga, sino que se cargara el código de cada acción o observador cuando la utilices. ( lazy Loader)

La función registerLib, espera como parámetros el nombre de la librería, un objeto con los nombre de acciones y con los nombre de los observadores a cargar de esa libreria, y la función para la carga dynamica de las acciones y observadores

import { registerLib } from '@rxmap/rxmap';

registerLib(‘test’,{
    observers:[‘test’],
    actions:[‘example’]
},
(type, mapLib, version, key) => import(/* webpackMode: "lazy" */ `./${type}/${mapLib}@${version}/${key}`);
);

Idealmente las librerías incluyen en default un array con los parámetros ya preparados.

import { registerLib } from '@rxmap/rxmap';
import rxLib from '@rxmap/rxmap/lib';

registerLib(...rxLib);

Otra forma de tener acciones o observadores es registrándolos para poder utilizarlos,pero de esta forma no tendrás la carga dynamica I tendrás que gestionar ,qual cargas según la librería de mapas que estes utilizando. Al contrario las librerías gestionan esto de manera automática. Consulta la librería que utilizas para saber que mapas soporta.

Acciones

Las acciones son funciones “puras” que realizan acciones sobre el mapa, la idea es que solo realicen una única acción para que sean mucho más fáciles de reaprovechar , probar y debugar. Ponemos "puras" entrecomilladas porque realmente la mayoria no pueden ser puras al realizar manipulaciones del Dom inderectamente al crear objetos del mapa. Las acciones se pueden invocar sobre RxMap o sobre un observador. Las acciones se pueden anidar entre ellas. Cada invocación de acciones es asíncrona pero dentro de cada secuencia en síncrona. Cada acción recibe el valor de la acción anterior para poder concatenar acciones.

RxMap.test();

RxMap.test2().test();

RxMap.observer(from(1)).test();

Funciones

Las funciones deberian ser funciones “puras” que pueden apliar la funcionalidad de RxMap, la idea es que solo realicen una única acción para que sean mucho más fáciles de reaprovechar , probar y debugar. Las funciones se pueden invocar sobre RxMap. Las funciones cuando se cargan desde una libreria son siempre asincronas.

RxMap.test();

Observadores

Los observadores son funciones que devuelven un observador ( RxJs ) para por ejemplo , gestionar los eventos del mapa como puede ser el click, o para gestionar datos que se pueden recibir continuamente como la posición GPS, el centro del mapa, etc... Con los observadores se pueden utilizar todos los operadores de RxJs para modificar, transformar , etc.. los valores del observador.

También se pueden utilizar observadores creados con RxJs para utilizar las acciones de RxMap. Cuando se utiliza un observador, se puede pasar como parámetro a la acción una función que transforme los argumentos en un array con los argumentos que espera la acción.

RxMap.observer(‘<observer name>’).subscribe((data) => console.log);

RxMap.observer(‘gps’).pipe(take(2)).subscribe(data=>console.log(data));

RxMap.observer(from([{lat:1,lng:2},{lat:2,lng:3}]).marker(data=>([data.lat,data.lng])).subscribe()


Middlewares

RxMap también te permite usar middlewares, para realizar acciones como:

* Logger de las funciones
* Realizar acciones antes o después de la ejecución de las acciones.
* Aplicar transformaciones
* Etc...

Un middleware lo puedes aplicar sobre todas las acciones o sobre una acción en concreto. Si al registrar el middleware el primer parámetro es un string, sólo se aplicará a la acción con ese nombre. Si el primer parámetro es el middleware se aplicará a todas las acciones.

Se pueden aplicar todos los middleware que se deseen.


registerMiddleware(LoggerMiddleware);

O

registerMiddleware('marker', TimerMiddleware);

Observar acciones

Con RxMap puedes observar las acciones para poder saber cuando se llama una acción concreta o un grupo de acciones con una expresión RegEx y realizar otra operación.


RxMap.observerAction('marker').subscribe((data) => {
    console.log("Action Subscriber 1 got data >>>>> ", data);
  });


RxMap.observerAction('.').subscribe((data) => {
    console.log("Action Subscriber 2 got data >>>>> ", data);
  });

Store Observable

RxMap dispone de un store que te permite guardar información y observarla para realizar acciones cuando esta información se modifique.

RxMap.observerStore(‘test’).subscribe((data) => {
    console.log("Store Subscriber 1 got data >>>>> ", data);
  });

RxMap.observerStore(‘.’).subscribe((data) => {
    console.log("Store Subscriber 2 got data >>>>> ", data);
  });

RxMap.store.test = ‘example’;

RxMap.store.example = 5;

const t = RxMap.store.test;


Con estas opciones te permite generar un código mucho más desacoplado de la librería de mapas que quieras utilizar y de esas forma tu código tendrá una vida mucho más larga, pudiendo actualizar cada versión de la librería o cambiando de ella con un esfuerzo mucho menor.

Creación

Como crear acciones

Una acción es una función que recibe el contexto y devuelve una funcion con 0 o n parámetros que es la que ejecutara los cambios sobre el mapa y devuelve el resultado o una promesa, puede ser una función async. Los objetivos de las acciones son interaccionar con el mapa. Es recomendable que sólo utilicen un método o propiedad sobre el mapa para que sea más fácil el desacoplarse. Se pueden crear acciones que llamen a otras acciones.

Las acciones deben tener el mínimo posible de dependencias. Es recomendable que cada acción se ubicaque en un fichero. Las acciones se tienen que exportar como default y se tiene que exportar la propiedad “name” con el nombre de la acción. Este nombre será el nombre con en el que se registre y se utilizará para invocarla desde RxMap o desde los observadores.

A todas las acciones se les inyecta como primer parámetro el contexto que es un objeto que incluye:

*  RxMap
* La librería Nativa del Mapa
* El valor de la anterior acción
* El store

Un ejemplo de acción sería.

 const setCenter = context => (paramA, paramB) => {
       const map = context.source.getMap();
         const oldCenter = map.getCenter();
         map.setCenter(paramA,paramB);
         return map;
};

export default setCenter;

Para poder utilizar esta acción hay dos formas. La puedes registrar manualmente o tenerla en una libreria y registrar la libreria.

Para registrar manualmente una acción se tendría que hacer lo siguiente.

import { registerAction, RxMap } from ‘@rxmap/rxmap’;
import { name }, action from ‘./actionExample’;

registerAction(name,action);

RxMap.load(‘leaflet’);
RxMap.create(‘mapId’,0,0,8);
// aquí ya podríamos utilizar la acción 
RxMap.setCenter(2.35,42.112);

Si registramos las acciones no estaremos utilizando la capacidad de cargar el código de manera dynamica. Sino que se incluirán en el bundle principal de la aplicación.

Como crear funciones

Una funcion es una función que recibe el contexto y devuelve una funcion con 0 o n parámetros que es la que ejecutara los cambios sobre el mapa y devuelve el resultado o una promesa, puede ser una función async. Se pueden crear funciones que llamen a otras funciones o a acciones.

Las funciones deben tener el mínimo posible de dependencias. Es recomendable que cada funcion se ubicaque en un fichero. Las funciones se tienen que exportar como default y se tiene que exportar la propiedad “name” con el nombre de la funcion. Este nombre será el nombre con en el que se registre y se utilizará para invocarla desde RxMap.

A todas las funciones se les inyecta como primer parámetro el RxMap.

Un ejemplo de acción sería.

 const test = context => (paramA, paramB) => {
    console.log(paramA);
    return 'test'
};

export default test;

Para poder utilizar esta funcion hay dos formas. La puedes registrar manualmente o tenerla en una libreria y registrar la libreria.

Para registrar manualmente una acción se tendría que hacer lo siguiente.

import { registerFunction, RxMap } from ‘@rxmap/rxmap’;
import { name }, action from ‘./actionExample’;

registerFunction(name,action);

RxMap.load(‘leaflet’);
RxMap.create(‘mapId’,0,0,8);
// aquí ya podríamos utilizar la funcion 
RxMap.test(2.35,42.112);

Si registramos las funciones no estaremos utilizando la capacidad de cargar el código de manera dynamica. Sino que se incluirán en el bundle principal de la aplicación.

Como crear observadores

Un observador es una función que recibe el contexto y devuelve una funciona que puede o no recibir parámetros y que devuelve siempre un observador RxJS

La idea de los observadores es reemplazar los eventos para poder tener más control sobre las cosas que pasan sobre el mapa. Y poder encadenar de manera mucho más simple acciones a partir de cosas que pasen. Con los observadores se pueden utilizar todos los transformadores y operadores de RxJS.

Los observadores deben tener el mínimo posible de dependencias. Es recomendable que cada observador se ubicaque en un fichero. Los observadores se tienen que exportar como default y se tiene que exportar la propiedad “name” con el nombre del observador. Este nombre será el nombre con en el que se registre y será el nombre con el que se recuperar el observador.

A todos los observadores se les llama primero con el contexto que incluye:

*  RxMap
* La librería Nativa del Mapa
* El valor de la anterior acción
* El store

Un ejemplo de observador podría ser:

import { fromEventPattern } from 'rxjs/internal/observable/fromEventPattern';
import { map } from 'rxjs/internal/operators/map';

const event = context => () => {
  const googleMaps = context.library.maps;
  const map_ = context.source.getMap();

  const addClickHandler = handler => map_.addListener('center_changed', handler);
  const removeClickHandler = (handler, listener) => googleMaps.event.removeListener(listener);


  return fromEventPattern(
    addClickHandler,
    removeClickHandler,
  ).pipe(map(() => {
    const center = map_.getCenter();
    return { lat: center.lat(), lng: center.lng() };
  }));
};

export default event;
/**
 * @private
*/
export const name = 'center';

Para poder utilizar este observador hay dos formas. La puedes registrar manualmente o tenerlo en una libreria y registrar la libreria.

Para registrar manualmente el observador se tendría que hacer lo siguiente.

import { registerObserver, RxMap } from ‘@rxmap/rxmap’;
import { name }, observer from ‘./observerExample’;

registerObserver(name,observer);

RxMap.load(‘leaflet’);
RxMap.create(‘mapId’,0,0,8);
// aquí ya podríamos utilizar la acción 
RxMap.observer(‘center’).subscribe((data)=>{
   console.log(data);
});

Si registramos los observadores manualmente no estaremos utilizando la capacidad de cargar el código de manera dynamica. Sino que se incluirán en el bundle principal de la aplicación.

Como crear un middleware

Los middleware son funciones que devuelve una función, El middleware recibe como parámetro la siguiente acción a ejecutar y la función que devuelve debe ser asíncrona y recibe como parámetros el CommandBus que esta realizando la ejecución y los argumentos de la función en un array. La función debe devolver el resultado de la ejecución de la acción.

Desde el CommandBus se puede recuperar el contexto con getContext() y el nombre de la función que esta ejecutando con getActionName().

En el middleware puedes hacer aciones antes de llamar a la acción a ejecutar. Y puedes modificar o hacer acciones después de ejecutar la acción.

Un ejemplo de middleware podría ser el logger de las acciones:


export const LoggerMiddleware = next => async (CommandBus, args) => {
  const actionName = commandBus.getActionName();
  const now = new Date().getTime();
  const name = `Command ${actionName} [${now}]: `;
  console.log(`Pre ${name}`, args);
  const res = await next(Map, args);
  console.log(`Post ${name}`, res);
  return res;
};

Los middleware se tienen no se cargan dinámicamente se tienen que importar específicamente.

Como crear una libreria

Las librerías se pueden crear dentro de tu proyecto o como un proyecto independiente para poder reaprovecharlas en otros proyectos.

Las librerías son un conjunto de acciones y observadores que ofrecen la misma funcionalidad sobre uno o diferentes mapas.

Idealmente tienen la siguiente estructura

<libName><type><mapType>:<version>/<actionName>

Example: lib_actions_google@latest_setCenter.js o lib_observer_google@latest_center.js

En las librerías cada acción o observador debe estar en un único fichero y cada fichero debe exponer en default la acción y debe exponer la propiedad name con el nombre de la acción

Las librerías tienen que tener un fichero index.js que incluya lo siguiente:

* la función de carga dynamica de sus acciones y observadores. Esta función recibirá como parámetros el tipo [observer|action] , el nombre de la libreria de mapas, la versión ( latest por defecto) y la clave o nombre de la acción o el observador. Se recomienda que se llame  ‘func’.
const func = (type, mapLib, version, key) => import(/* webpackMode: "lazy" */ `./${type}/${mapLib}@${version}/${key}`);

* Array con el nombre de los observadores. Se recomienda que se llame  ‘observers’
* Array con el nombre de las acciones. Se recomienda que se llame  ‘actions’
* El nombre de la librería. Se recomienda que se llame  ‘name’.

Y es recomendable devolver en el parámetro por defecto un array con los datos en el orden que pide la función de registrar libreria.

Adjunto un ejemplo del fichero index.js


export const name = 'rxmap';
export const actions = ['addData', 'create', 'marker', 'point', 'popup', 'setCenter'];
export const observers = ['gps', 'center', 'click'];
export const func = (type, mapLib, version, key) => import(/* webpackMode: "lazy" */ `./${type}/${mapLib}@${version}/${key}`);

export default [
  name,
  {
    observers,
    actions,
  },
  func,
];

Cuando se compila las librerías tienen que inlcuir la version con Módulos ( Es2015 ) y la version UMD, en la versión UMD que suele ser la que se pública en servicios como unpkg la ruta de la carga de librerías dinámicas deber ser con la url completa no relativa.

Build

Para poder realizar la construcción del bundle de manera correcta, se necesita utilizar webPack por desgracia actualmente ni Rollup ni Parcel funciona correctamente con los imports dinámicos, esperamos que esto cambien en breve.

Para realizar el build con Angular, si queremos disponer de la carga dinamica, hay que modificar los siguientes parámetros.

* En el fichero tsconfig.app.json hay que poner el valor ‘module’ a ‘esnext’

Ejemplos

Lista de ejemplos disponibles en github.

Librerias

Librerías para añadir acciones y observadores:

Para añadir una nueva librería enviar un mail a rxmap@xlab.tech o realiza un pullRequest de la documentación con la libreria añadida. Para hacer un PullRequest:

  • Haz un fork del repositorio.
    • Haz commit y push de los cambios en tu reposition.
    • Crear un PullRequest .