Commit 1ddaf255 authored by Pedro Eduardo Trujillo's avatar Pedro Eduardo Trujillo
Browse files

Estrena componente global de selección

Implementa un nuevo componente SelectionManager a nivel global de la
aplicación. Se diferencia del actual componente Selector en que el nuevo
se enfoca en la selección de elementos local (sin persistencia en ningún
servicio), mientras que el existente tenía esta funcionalidad implícita.
Además, Selector es instanciado globalmente en la parte interna de la
app, mientras que SelectionManager escala un nivel hasta App (tanto
parte externa como interna).

Incluye base común para los componentes que necesiten comunicarse con el
nuevo SelectionManager, así como un canal global conocido por todos.
parent 0247c9f0
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ define([
	, 'src/app/component/meta/MetaTags'
	, 'src/app/component/ModuleStore'
	, 'src/app/component/request/RestManagerXhrImpl'
	, 'src/app/component/select/SelectionManager'
	, 'src/app/component/Router'
	, 'src/app/util/CheckBrowser'
	, 'src/component/base/_Module'
@@ -34,6 +35,7 @@ define([
	, MetaTags
	, ModuleStore
	, RestManagerXhrImpl
	, SelectionManager
	, Router
	, CheckBrowser
	, _Module
@@ -163,6 +165,10 @@ define([
			new Auth({
				parentChannel
			});

			new SelectionManager({
				parentChannel
			});
		},

		_defineSubscriptions: function() {
+230 −0
Original line number Diff line number Diff line
define([
	'dojo/_base/declare'
	, 'src/component/base/_Module'
], function(
	declare
	, _Module
) {

	return declare(_Module, {
		// summary:
		//   Componente encargado de centralizar la selección de elementos.

		// _selection: Object
		//   Contiene los identificadores de los elementos seleccionados, indexados por target.

		postMixInProperties: function() {

			const defaultConfig = {
				ownChannel: 'selectionManager',
				events: {
					ITEM_SELECTED: 'itemSelected',
					ITEM_DESELECTED: 'itemDeselected',
					SELECTION_GOT: 'selectionGot',
					SELECTION_CLEARED: 'selectionCleared'
				},
				actions: {
					SELECT_ITEM: 'selectItem',
					ITEM_SELECTED: 'itemSelected',
					DESELECT_ITEM: 'deselectItem',
					ITEM_DESELECTED: 'itemDeselected',
					GET_SELECTION: 'getSelection',
					SELECTION_GOT: 'selectionGot',
					CLEAR_SELECTION: 'clearSelection',
					SELECTION_CLEARED: 'selectionCleared',
					SELECT_SINGLE_ITEM: 'selectSingleItem'
				},
				_selection: {}
			};

			this._mergeOwnAttributes(defaultConfig);

			this.inherited(arguments);
		},

		_defineSubscriptions: function() {

			const options = {
				predicate: req => this._chkTargetIsValid(req)
			};

			this.subscriptionsConfig.push({
				channel: this.getChannel('SELECT_ITEM'),
				callback: '_subSelectItem',
				options
			},{
				channel: this.getChannel('DESELECT_ITEM'),
				callback: '_subDeselectItem',
				options
			},{
				channel: this.getChannel('GET_SELECTION'),
				callback: '_subGetSelection',
				options
			},{
				channel: this.getChannel('CLEAR_SELECTION'),
				callback: '_subClearSelection',
				options
			},{
				channel: this.getChannel('SELECT_SINGLE_ITEM'),
				callback: '_subSelectSingleItem',
				options
			});
		},

		_definePublications: function() {

			this.publicationsConfig.push({
				event: 'ITEM_SELECTED',
				channel: this.getChannel('ITEM_SELECTED')
			},{
				event: 'ITEM_DESELECTED',
				channel: this.getChannel('ITEM_DESELECTED')
			},{
				event: 'SELECTION_GOT',
				channel: this.getChannel('SELECTION_GOT')
			},{
				event: 'SELECTION_CLEARED',
				channel: this.getChannel('SELECTION_CLEARED')
			});
		},

		_subSelectItem: function(req, _mediatorChannel, componentInfo) {

			const requesterChannel = this._getRequesterChannel(componentInfo);

			this._performSelectItem(req, requesterChannel);
		},

		_subDeselectItem: function(req, _mediatorChannel, componentInfo) {

			const requesterChannel = this._getRequesterChannel(componentInfo);

			this._performDeselectItem(req, requesterChannel);
		},

		_subGetSelection: function(req, _mediatorChannel, componentInfo) {

			const requesterChannel = this._getRequesterChannel(componentInfo);

			this._performGetSelection(req, requesterChannel);
		},

		_subClearSelection: function(req, _mediatorChannel, componentInfo) {

			const requesterChannel = this._getRequesterChannel(componentInfo);

			this._performClearSelection(req, requesterChannel);
		},

		_subSelectSingleItem: function(req, _mediatorChannel, componentInfo) {

			const requesterChannel = this._getRequesterChannel(componentInfo);

			this._performSingleSelectItem(req, requesterChannel);
		},

		_getRequesterChannel: function(componentInfo) {

			return componentInfo?.publisherChannel ?? '';
		},

		_performSelectItem: function(req, requesterChannel) {

			const target = req.target,
				itemId = req.itemId,
				item = req.item;

			this._addItemIdToSelection(target, itemId);

			this._emitEvt('ITEM_SELECTED', {
				target,
				itemId,
				item
			});
		},

		_addItemIdToSelection: function(target, itemId) {

			this._selection[target] = this._selection[target] ?? {itemIds: {}};

			this._selection[target].itemIds[itemId] = true;
		},

		_performDeselectItem: function(req, requesterChannel) {

			const target = req.target,
				itemId = req.itemId,
				item = req.item;

			this._removeItemIdFromSelection(target, itemId);

			this._emitEvt('ITEM_DESELECTED', {
				target,
				itemId,
				item
			});
		},

		_removeItemIdFromSelection: function(target, itemId) {

			delete this._selection[target]?.itemIds?.[itemId];
		},

		_performGetSelection: function(req, requesterChannel) {

			const target = req.target,
				requestedItemId = req.itemId,
				itemIds = this._getItemIdsForGetSelection(target, requestedItemId);

			this._emitEvt('SELECTION_GOT', {
				target,
				itemIds
			});
		},

		_getItemIdsForGetSelection: function(target, itemId) {

			if (!itemId) {
				return this._getSelectedItemIds(target);
			}

			return this._isItemIdSelected(target, itemId) ? [itemId] : [];
		},

		_getSelectedItemIds: function(target) {

			return Object.keys(this._selection[target]?.itemIds ?? {});
		},

		_isItemIdSelected: function(target, itemId) {

			return this._selection[target]?.itemIds?.[itemId] ?? false;
		},

		_performClearSelection: function(req, requesterChannel) {

			const target = req.target;

			this._emptyTargetSelection(target);

			this._emitEvt('SELECTION_CLEARED', {
				target
			});
		},

		_emptyTargetSelection: function(target) {

			delete this._selection[target];
		},

		_performSingleSelectItem: function(req, requesterChannel) {

			const target = req.target,
				prevItemIds = this._getSelectedItemIds(target);

			prevItemIds?.forEach(itemId => this._performDeselectItem({target, itemId}, requesterChannel));

			this._performSelectItem(req, requesterChannel);
		}
	});
});
+6 −1
Original line number Diff line number Diff line
@@ -110,6 +110,8 @@ define([
		//		Nombre del canal por donde se van a gestionar los cambios de ruta.
		//	storeChannel: String
		//		Nombre del canal por donde se van a recibir los datos.
		//	selectionManagerChannel: String
		//		Nombre del canal por donde se van a seleccionar los elementos.
		//	selectorChannel: String
		//		Nombre del canal por donde se van a seleccionar los items.
		//	credentialsChannel: String
@@ -157,6 +159,7 @@ define([
				globalOwnChannels: {
					ROUTER: "router",
					STORE: "data",
					SELECTION_MANAGER: "selectionManager",
					SELECTOR: "selection",
					CREDENTIALS: "credentials",
					EXTERNAL_CONFIG: "externalConfig",
@@ -222,7 +225,9 @@ define([
			this.alertChannel = this._buildChannel(this.rootChannel, this.globalOwnChannels.ALERT);
			this.communicationChannel = this._buildChannel(this.rootChannel, this.globalOwnChannels.COMMUNICATION);
			this.authChannel = this._buildChannel(this.rootChannel, this.globalOwnChannels.AUTH);
			this.externalConfigChannel = this._buildChannel(this.rootChannel, this.globalOwnChannels.EXTERNAL_CONFIG),
			this.externalConfigChannel = this._buildChannel(this.rootChannel, this.globalOwnChannels.EXTERNAL_CONFIG);
			this.selectionManagerChannel = this._buildChannel(this.rootChannel,
				this.globalOwnChannels.SELECTION_MANAGER);

			this.outerAppChannel = this._buildChannel(this.rootChannel, this.outerAppOwnChannel);
			this.innerAppChannel = this._buildChannel(this.rootChannel, this.innerAppOwnChannel);
+118 −0
Original line number Diff line number Diff line
define([
	'dojo/_base/declare'
	, 'src/component/base/_SelectionManagerItfc'
], function(
	declare
	, _SelectionManagerItfc
) {

	return declare(_SelectionManagerItfc, {
		// summary:
		//   Base común para todos los componentes que trabajan con selección local de elementos.

		postMixInProperties: function() {

			const defaultConfig = {
				events: {
					SELECT_ITEM: 'selectItem',
					DESELECT_ITEM: 'deselectItem',
					GET_SELECTION: 'getSelection',
					CLEAR_SELECTION: 'clearSelection',
					SELECT_SINGLE_ITEM: 'selectSingleItem'
				},
				actions: {
					SELECT_ITEM: 'selectItem',
					ITEM_SELECTED: 'itemSelected',
					DESELECT_ITEM: 'deselectItem',
					ITEM_DESELECTED: 'itemDeselected',
					GET_SELECTION: 'getSelection',
					SELECTION_GOT: 'selectionGot',
					CLEAR_SELECTION: 'clearSelection',
					SELECTION_CLEARED: 'selectionCleared',
					SELECT_SINGLE_ITEM: 'selectSingleItem'
				}
			};

			this._mergeOwnAttributes(defaultConfig);

			this.inherited(arguments);
		},

		_defineSubscriptions: function() {

			this.inherited(arguments);

			const options = {
				predicate: req => this._chkTargetIsValid(req)
			};

			this.subscriptionsConfig.push({
				channel: this._buildChannel(this.selectionManagerChannel, 'ITEM_SELECTED'),
				callback: '_subItemSelected',
				options
			},{
				channel: this._buildChannel(this.selectionManagerChannel, 'ITEM_DESELECTED'),
				callback: '_subItemDeselected',
				options
			},{
				channel: this._buildChannel(this.selectionManagerChannel, 'SELECTION_GOT'),
				callback: '_subSelectionGot',
				options
			},{
				channel: this._buildChannel(this.selectionManagerChannel, 'SELECTION_CLEARED'),
				callback: '_subSelectionCleared',
				options
			});
		},

		_definePublications: function() {

			this.inherited(arguments);

			this.publicationsConfig.push({
				event: 'SELECT_ITEM',
				channel: this._buildChannel(this.selectionManagerChannel, 'SELECT_ITEM')
			},{
				event: 'DESELECT_ITEM',
				channel: this._buildChannel(this.selectionManagerChannel, 'DESELECT_ITEM')
			},{
				event: 'GET_SELECTION',
				channel: this._buildChannel(this.selectionManagerChannel, 'GET_SELECTION')
			},{
				event: 'CLEAR_SELECTION',
				channel: this._buildChannel(this.selectionManagerChannel, 'CLEAR_SELECTION')
			},{
				event: 'SELECT_SINGLE_ITEM',
				channel: this._buildChannel(this.selectionManagerChannel, 'SELECT_SINGLE_ITEM')
			});
		},

		_subItemSelected: function(res) {

			this._onItemSelected(res);
		},

		_subItemDeselected: function(res) {

			this._onItemDeselected(res);
		},

		_subSelectionGot: function(res) {

			const itemIds = res?.itemIds,
				target = res?.target;

			itemIds?.forEach(itemId => this._onItemSelected({itemId, target}));
		},

		_subSelectionCleared: function(res) {

			this._onSelectionCleared(res);
		},

		_getSelectionTarget: function() {

			return this.selectionTarget ?? this._getTarget?.() ?? this.target;
		}
	});
});
+26 −0
Original line number Diff line number Diff line
define([
	"dojo/_base/declare"
	, "dojo/_base/lang"
	, "src/component/base/_Itfc"
], function(
	declare
	, lang
	, _Itfc
){
	return declare(_Itfc, {
		//	summary:
		//		Interfaz de _Selection.
		//	description:
		//		Define los métodos que debe poseer el módulo o la implementación.


		_getMethodsToImplement: function() {

			return lang.mixin(this.inherited(arguments), {
				"_onItemSelected": {},
				"_onItemDeselected": {},
				"_onSelectionCleared": {}
			});
		}
	});
});