Commit 500af026 authored by Pedro Eduardo Trujillo's avatar Pedro Eduardo Trujillo
Browse files

Separa módulo Router en nuevo App, reorganiza

Desde hace mucho, el módulo Router se había convertido en el centro de
la aplicación, encargándose de más cosas que las que su nombre haría
pensar a cualquiera. Para aclarar este punto e independizar la gestión
de la navegación del resto de manejo de módulos, se crea un nuevo módulo
App, que ocupa el lugar de Router como capa inicial y también a nivel de
canales mediator.

Fix #51.
parent 3dfe53c7
Loading
Loading
Loading
Loading
+68 −203
Original line number Diff line number Diff line
define([
	'app/innerApp'
	, 'app/outerApp'
	, 'app/components/CookieLoader'
	, 'app/components/ModuleStore'
	'app/components/CookieLoader'
	, 'app/redmicConfig'
	, 'dojo/_base/declare'
	, 'dojo/_base/lang'
	, 'dojo/dom'
	, 'dojo/dom-attr'
	, 'dojo/has'
	, 'dojo/io-query'
	, 'dojo/mouse'
	, 'put-selector/put'
	, 'redmic/base/CheckBrowser'
	, 'redmic/modules/app/innerApp'
	, 'redmic/modules/app/ModuleStore'
	, 'redmic/modules/app/outerApp'
	, 'redmic/modules/app/Router'
	, 'redmic/modules/notification/CommunicationCenter'
	, 'redmic/modules/notification/Alert'
	, 'redmic/modules/base/Credentials'
@@ -24,20 +22,18 @@ define([
	, 'redmic/modules/store/RestManagerImpl'
	, 'templates/LoadingCustom'
], function(
	InnerApp
	, OuterApp
	, CookieLoader
	, ModuleStore
	CookieLoader
	, redmicConfig
	, declare
	, lang
	, dom
	, domAttr
	, has
	, ioQuery
	, mouse
	, put
	, CheckBrowser
	, InnerApp
	, ModuleStore
	, OuterApp
	, Router
	, CommunicationCenter
	, Alert
	, Credentials
@@ -51,9 +47,9 @@ define([
) {

	var rootNode = dom.byId('rootContainer'),
		nativeLoadingNode = dom.byId('loadingContainer'),
		nativeLoadingNode = dom.byId('loadingContainer');

		getGlobalContext = function() {
	var getGlobalContext = function() {

		if (has('host-browser')) {
			return window;
@@ -62,9 +58,9 @@ define([
		} else {
			console.error('Environment not supported');
		}
		},
	};

		hideNativeLoadingNode = function() {
	var hideNativeLoadingNode = function() {

		if (nativeLoadingNode) {
			put('!', nativeLoadingNode);
@@ -81,16 +77,14 @@ define([

	return declare(_Module, {
		//	summary:
		//		Módulo encargado de controlar el acceso a la aplicación.
		//		Módulo encargado de gestionar los componentes principales de la aplicación.
		//	description:
		//		Escucha las rutas accedidas por el usuario. Diferencia entre los destinos pertenecientes a la parte
		//		externa (antes de obtener permisos) y a la parte interna (después de identificarse, aunque sea como
		//		usuario invitado). Maneja las instancias de layout de aplicación y de los módulos cargados dentro de
		//		dichos layouts.

		//	paths: Object
		//		Constantes de rutas base
		//		Crea las instancias de los módulos que componen a la aplicación y coordina su funcionamiento. Maneja
		//		los accesos a las partes interna y externa, junto con los cambios de contenido principal a medida que el
		//		usuario navega por la aplicación.

		//	_router: Object
		//		Instancia del módulo de control de rutas de acceso.
		//	_credentials: Object
		//		Instancia del módulo de control de permisos y accesos de usuario.
		//	_moduleStore: Object
@@ -106,8 +100,6 @@ define([
		//		Clave del módulo actual dentro de moduleStore.
		//	_prevModuleKey: String
		//		Clave del módulo antiguo dentro de moduleStore.
		//	_userFound: Boolean
		//		Indica si hay presente algún token de usuario.

		constructor: function(args) {

@@ -116,62 +108,59 @@ define([
				events: {
					GET_CREDENTIALS: 'getCredentials',
					GET_MODULE: 'getModule',
					GET_QUERY_PARAMS: 'getQueryParams',
					CLEAR_MODULE: 'clearModule'
				},
				actions: {
					GET_QUERY_PARAMS: 'getQueryParams',
					GOT_QUERY_PARAMS: 'gotQueryParams'
					CHANGE_MODULE: 'changeModule'
				},

				paths: {
					ERROR: '/404',
					ROOT: '/',
					HOME: 'home',
					LOGIN: 'login'
				},
				_reconnectTimeout: 10000
			};

			lang.mixin(this, this.config, args);

			new CookieLoader();
			this._router = new Router({
				parentChannel: this.getChannel(),
				globalContext: getGlobalContext()
			});

			this._setRouterListeners();
			new CookieLoader();
		},

		_initialize: function() {

			var parentChannel = this.getChannel();

			new RestManagerImpl({
				parentChannel: this.getChannel()
				parentChannel: parentChannel
			});

			new CommunicationCenter({
				parentChannel: this.getChannel()
				parentChannel: parentChannel
			});

			new Alert({
				parentChannel: this.getChannel()
				parentChannel: parentChannel
			});

			new Analytics({
				parentChannel: this.getChannel()
				parentChannel: parentChannel
			});

			new MetaTags({
				parentChannel: this.getChannel()
				parentChannel: parentChannel
			});

			this._credentials = new Credentials({
				parentChannel: this.getChannel()
				parentChannel: parentChannel
			});

			this._moduleStore = new ModuleStore({
				parentChannel: this.getChannel()
				parentChannel: parentChannel
			});

			this._loading = new Loading({
				parentChannel: this.getChannel(),
				parentChannel: parentChannel,
				globalNode: rootNode
			});
		},
@@ -179,6 +168,9 @@ define([
		_defineSubscriptions: function() {

			this.subscriptionsConfig.push({
				channel : this.getChannel('CHANGE_MODULE'),
				callback: '_subChangeModule'
			},{
				channel : this._credentials.getChannel('AVAILABLE'),
				callback: '_subAvailableCredentials'
			},{
@@ -190,9 +182,6 @@ define([
			},{
				channel : this._moduleStore.getChannel('AVAILABLE_MODULE'),
				callback: '_subAvailableModule'
			},{
				channel : this.getChannel('GET_QUERY_PARAMS'),
				callback: '_subGetQueryParams'
			});
		},

@@ -207,9 +196,6 @@ define([
			},{
				event: 'CLEAR_MODULE',
				channel: this._moduleStore.getChannel('CLEAR_MODULE')
			},{
				event: 'GET_QUERY_PARAMS',
				channel: this.getChannel('GOT_QUERY_PARAMS')
			});
		},

@@ -220,95 +206,35 @@ define([
			this.inherited(arguments);
		},

		_setRouterListeners: function() {
			//	summary:
			//		Prepara la escucha en toda la aplicación de los eventos requeridos para controlar la navegación en
			//		una sola página
			//	tags:
			//		private

			var gCtx = getGlobalContext(),
				dCtx = gCtx.document,
				listenMethod, eventPrefix;

			if (gCtx.addEventListener) {
				listenMethod = dCtx.addEventListener;
				eventPrefix = '';
			} else {
				listenMethod = dCtx.attachEvent;
				eventPrefix = 'on';
			}
		_subCredentialsRemoved: function() {

			listenMethod.call(dCtx, eventPrefix + 'click', lang.hitch(this, this._evaluateClickEvt));
			listenMethod.call(gCtx, eventPrefix + 'popstate', lang.hitch(this, this._evaluatePopStateEvt));
			this._publish(this._router.getChannel('GO_TO_ROOT_ROUTE'), {
				userGone: true
			});
		},

		_evaluateClickEvt: function(evt) {
			//	summary:
			//		Recibe eventos de click y, en caso de detectar un enlace de navegación interno, lo captura
			//	tags:
			//		private

			var event = evt || getGlobalContext().event,
				targets = this._getClickTargets(event);

			for (var i = 0; i < targets.length; i++) {
				var target = targets[i],
					targetIsNotAppHref = !target || target.nodeName !== 'A' || !domAttr.get(target, 'd-state-url');

				if (targetIsNotAppHref) {
					continue;
				}
		_subAvailableCredentials: function(res) {

				this._handleAppHref(event, target);
				break;
			}
			this._publish(this._router.getChannel('EVALUATE_ROUTE'), {
				userFound: res.found
			});
		},

		_handleAppHref: function(event, target) {

			var url = target.pathname + target.search + target.hash;

			if (mouse.isMiddle(event)) {
				var gCtx = getGlobalContext(),
					newPageUrl = target.protocol + '//' + target.hostname + url;

				gCtx.open(newPageUrl, '_blank');
			} else {
				this._addHistory(url);
				this._onRouteChange();
			}
		_subCredentialsRequestFailed: function() {

			if (event.preventDefault) {
				event.preventDefault();
			} else {
				event.returnValue = false;
			if (!this._reconnectingMessageNode) {
				this._showReconnectingMessage();
			}
		},

		_addHistory: function(value) {

			getGlobalContext().history.pushState(null, null, value);
			setTimeout(lang.hitch(this, this._emitEvt, 'GET_CREDENTIALS'), this._reconnectTimeout);
		},

		_onRouteChange: function() {
		_subChangeModule: function(req) {

			var locationObj = getGlobalContext().location,
				locationPath = locationObj.pathname,
				route = locationPath.substr(1),
				routeIsEmpty = !route || route === '' || route === this.paths.ROOT,
				loginWasSuccessful = route === this.paths.LOGIN && this._userFound;
			var route = req.route,
				locationQuery = req.locationQuery,
				routeChanged = this._changeModule(route);

			if (routeIsEmpty || loginWasSuccessful) {
				route = this.paths.HOME;
				this._addHistory(route);
			}

			var locationQuery = locationObj.search;

			this._handleQueryParameters(locationQuery.substr(1));

			var routeChanged = this._changeModule(route);
			if (routeChanged) {
				this._emitEvt('TRACK', {
					type: TRACK.type.page,
@@ -317,36 +243,6 @@ define([
			}
		},

		_evaluatePopStateEvt: function(evt) {
			//	summary:
			//		Recibe eventos de popstate para navegar por la aplicación usando los botones de retroceder/avanzar
			//	tags:
			//		private

			this._onRouteChange();
		},

		_subCredentialsRemoved: function() {

			delete this._userFound;
			getGlobalContext().location.href = this.paths.ROOT;
		},

		_subAvailableCredentials: function(res) {

			this._userFound = res.found;
			this._onRouteChange();
		},

		_subCredentialsRequestFailed: function() {

			if (!this._reconnectingMessageNode) {
				this._showReconnectingMessage();
			}

			setTimeout(lang.hitch(this, this._emitEvt, 'GET_CREDENTIALS'), this._reconnectTimeout);
		},

		_showReconnectingMessage: function() {

			var template = LoadingCustomTemplate({
@@ -360,29 +256,6 @@ define([
			hideNativeLoadingNode();
		},

		_handleQueryParameters: function(queryString) {

			this._currentQueryParams = this._getQueryParameters(queryString);

			this._removeQueryParametersFromHref();
		},

		_getQueryParameters: function(queryString) {

			return ioQuery.queryToObject(queryString);
		},

		_removeQueryParametersFromHref: function() {

			var locationObj = getGlobalContext().location,
				locationPort = locationObj.port,
				isNotStandardPort = locationPort !== '80',
				hrefPort = isNotStandardPort ? (':' + locationPort) : '',
				href = locationObj.protocol + '//' + locationObj.hostname + hrefPort + locationObj.pathname + locationObj.hash;

			getGlobalContext().history.replaceState(null, null, href);
		},

		_changeModule: function(route) {
			//	summary:
			//		Actualiza el módulo que se visualiza.
@@ -446,7 +319,7 @@ define([
			//		private

			if (!instance || !instance.getChannel) {
				getGlobalContext().location.href = this.paths.ERROR;
				this._publish(this._router.getChannel('GO_TO_ERROR_ROUTE'), {});
				return;
			}

@@ -463,14 +336,6 @@ define([
			});
		},

		_subGetQueryParams: function(req) {

			this._emitEvt('GET_QUERY_PARAMS', {
				requesterId: req.requesterId,
				queryParams: this._currentQueryParams || {}
			});
		},

		_closeModule: function() {
			//	summary:
			//		Cierra un módulo.
+256 −0
Original line number Diff line number Diff line
define([
	'dojo/_base/declare'
	, 'dojo/_base/lang'
	, 'dojo/dom-attr'
	, 'dojo/io-query'
	, 'dojo/mouse'
	, 'redmic/modules/base/_Module'
	, 'redmic/modules/base/_Store'
], function(
	declare
	, lang
	, domAttr
	, ioQuery
	, mouse
	, _Module
	, _Store
) {

	return declare(_Module, {
		//	summary:
		//		Módulo encargado de controlar el acceso a la aplicación.
		//	description:
		//		Escucha las rutas accedidas por el usuario, diferenciando si son de navegación interna a la app o si se
		//		deben cargar en el navegador. También coordina la obtención de parámetros recibidos en URL.

		//	paths: Object
		//		Constantes de rutas base
		//	globalContext: Object
		//		Contexto provisto por App para gestionar el entorno de ejecución
		//	_userFound: Boolean
		//		Indica si hay presente algún token de usuario.

		constructor: function(args) {

			this.config = {
				ownChannel: 'router',
				events: {
					GET_QUERY_PARAMS: 'getQueryParams'
				},
				actions: {
					CHANGE_MODULE: 'changeModule',
					EVALUATE_ROUTE: 'evaluateRoot',
					GO_TO_ROOT_ROUTE: 'goToRootRoute',
					GO_TO_ERROR_ROUTE: 'goToErrorRoute',
					GET_QUERY_PARAMS: 'getQueryParams',
					GOT_QUERY_PARAMS: 'gotQueryParams'
				},

				paths: {
					ERROR: '/404',
					ROOT: '/',
					HOME: 'home',
					LOGIN: 'login'
				}
			};

			lang.mixin(this, this.config, args);

			this._setRouterListeners();
		},

		_initialize: function() {

		},

		_defineSubscriptions: function() {

			this.subscriptionsConfig.push({
				channel : this.getChannel('EVALUATE_ROUTE'),
				callback: '_subEvaluateRoute'
			},{
				channel : this.getChannel('GO_TO_ROOT_ROUTE'),
				callback: '_subGoToRootRoute'
			},{
				channel : this.getChannel('GO_TO_ERROR_ROUTE'),
				callback: '_subGoToErrorRoute'
			},{
				channel : this.getChannel('GET_QUERY_PARAMS'),
				callback: '_subGetQueryParams'
			});
		},

		_definePublications: function() {

			this.publicationsConfig.push({
				event: 'GET_QUERY_PARAMS',
				channel: this.getChannel('GOT_QUERY_PARAMS')
			});
		},

		_setRouterListeners: function() {
			//	summary:
			//		Prepara la escucha en toda la aplicación de los eventos requeridos para controlar la navegación en
			//		una sola página
			//	tags:
			//		private

			var gCtx = this.globalContext,
				dCtx = gCtx.document,
				listenMethod, eventPrefix;

			if (gCtx.addEventListener) {
				listenMethod = dCtx.addEventListener;
				eventPrefix = '';
			} else {
				listenMethod = dCtx.attachEvent;
				eventPrefix = 'on';
			}

			listenMethod.call(dCtx, eventPrefix + 'click', lang.hitch(this, this._evaluateClickEvt));
			listenMethod.call(gCtx, eventPrefix + 'popstate', lang.hitch(this, this._evaluatePopStateEvt));
		},

		_evaluateClickEvt: function(evt) {
			//	summary:
			//		Recibe eventos de click y, en caso de detectar un enlace de navegación interno, lo captura
			//	tags:
			//		private

			var event = evt || this.globalContext.event,
				targets = this._getClickTargets(event);

			for (var i = 0; i < targets.length; i++) {
				var target = targets[i],
					targetIsNotAppHref = !target || target.nodeName !== 'A' || !domAttr.get(target, 'd-state-url');

				if (targetIsNotAppHref) {
					continue;
				}

				this._handleAppHref(event, target);
				break;
			}
		},

		_handleAppHref: function(event, target) {

			var url = target.pathname + target.search + target.hash;

			if (mouse.isMiddle(event)) {
				var gCtx = this.globalContext,
					newPageUrl = target.protocol + '//' + target.hostname + url;

				gCtx.open(newPageUrl, '_blank');
			} else {
				this._addHistory(url);
				this._onRouteChange();
			}

			if (event.preventDefault) {
				event.preventDefault();
			} else {
				event.returnValue = false;
			}
		},

		_addHistory: function(value) {

			this.globalContext.history.pushState(null, null, value);
		},

		_onRouteChange: function() {

			var locationObj = this.globalContext.location,
				locationPath = locationObj.pathname,
				route = locationPath.substr(1),
				routeIsEmpty = !route || route === '' || route === this.paths.ROOT,
				loginWasSuccessful = route === this.paths.LOGIN && this._userFound;

			if (routeIsEmpty || loginWasSuccessful) {
				route = this.paths.HOME;
				this._addHistory(route);
			}

			var locationQuery = locationObj.search;

			this._handleQueryParameters(locationQuery.substr(1));

			this._publish(this.getParentChannel('CHANGE_MODULE'), {
				route: route,
				locationQuery: locationQuery
			});
		},

		_evaluatePopStateEvt: function(evt) {
			//	summary:
			//		Recibe eventos de popstate para navegar por la aplicación usando los botones de retroceder/avanzar
			//	tags:
			//		private

			this._onRouteChange();
		},

		_subEvaluateRoute: function(req) {

			this._userFound = req.userFound;

			this._onRouteChange();
		},

		_subGoToRootRoute: function(req) {

			if (req.userGone) {
				delete this._userFound;
			}

			this._goToRootPage();
		},

		_subGoToErrorRoute: function() {

			this._goToErrorPage();
		},

		_subGetQueryParams: function(req) {

			this._emitEvt('GET_QUERY_PARAMS', {
				requesterId: req.requesterId,
				queryParams: this._currentQueryParams || {}
			});
		},

		_goToRootPage: function() {

			this.globalContext.location.href = this.paths.ROOT;
		},

		_goToErrorPage: function() {

			this.globalContext.location.href = this.paths.ERROR;
		},

		_handleQueryParameters: function(queryString) {

			this._currentQueryParams = this._getQueryParameters(queryString);

			this._removeQueryParametersFromHref();
		},

		_getQueryParameters: function(queryString) {

			return ioQuery.queryToObject(queryString);
		},

		_removeQueryParametersFromHref: function() {

			var locationObj = this.globalContext.location,
				locationPort = locationObj.port,
				isNotStandardPort = locationPort !== '80',
				hrefPort = isNotStandardPort ? (':' + locationPort) : '',
				href = locationObj.protocol + '//' + locationObj.hostname + hrefPort + locationObj.pathname + locationObj.hash;

				this.globalContext.history.replaceState(null, null, href);
		}
	});
});
Loading