commit 960133353dd93f44e4f7ad5f0956d8dff7a6baea
Author: Pavel Fidranský <fidransky@outlook.com>
Date:   Sat Nov 10 16:43:26 2018 +0100

    detach NodeRelatedArchetypeList to a separate component

diff --git a/sources/src/main/webapp/js/components/group.js b/sources/src/main/webapp/js/components/group.js
index d812788..80c015f 100644
--- a/sources/src/main/webapp/js/components/group.js
+++ b/sources/src/main/webapp/js/components/group.js
@@ -60,13 +60,9 @@ class Group extends Node {
 		});
 
 		let vertexRelatedArchetypeMap = vertex.relatedArchetypeMap;
-		for (let archetypeIndex in vertexRelatedArchetypeMap) {
-			if (this.relatedArchetypeMap.hasOwnProperty(archetypeIndex) === false) {
-				this.relatedArchetypeMap[archetypeIndex] = 0;
-			}
-
-			this.relatedArchetypeMap[archetypeIndex] += vertexRelatedArchetypeMap[archetypeIndex];
-		}
+		vertexRelatedArchetypeMap.forEach((value, key) => {
+			this._relatedArchetypeListComponent.add(key, value);
+		});
 
 		app.viewportComponent.removeNode(vertex);
 
@@ -99,9 +95,9 @@ class Group extends Node {
 		});
 
 		let vertexRelatedArchetypeMap = vertex.relatedArchetypeMap;
-		for (let archetypeIndex in vertexRelatedArchetypeMap) {
-			this.relatedArchetypeMap[archetypeIndex] -= vertexRelatedArchetypeMap[archetypeIndex];
-		}
+		vertexRelatedArchetypeMap.forEach((value, key) => {
+			this._relatedArchetypeListComponent.remove(key, value);
+		});
 
 		app.viewportComponent.addNode(vertex);
 
@@ -295,47 +291,6 @@ class Group extends Node {
 	 * @returns {HTMLElement} HTML DOM element.
 	 */
 	_renderExcluded() {
-		const svg = DOM.s('svg', {
-			xmlns: 'http://www.w3.org/2000/svg',
-			height: 60,
-			width: 46,
-		});
-
-		// related archetypes
-		const relatedArchetypesGroup = DOM.s('g', {
-			transform: 'translate(10, 15)',
-		});
-		svg.appendChild(relatedArchetypesGroup);
-
-		let archetypeIconOrder = 0;
-		for (let archetypeIndex in this.relatedArchetypeMap) {
-			relatedArchetypesGroup.appendChild(DOM.s('g', {
-				class: 'related-archetype',
-				transform: `translate(0, ${archetypeIconOrder * 20})`,
-			}, [
-				// counter
-				DOM.s('text', {}, [
-					DOM.t(this.relatedArchetypeMap[archetypeIndex]),
-				]),
-				// icon
-				DOM.s('use', {
-					href: '#vertexArchetypeIcon-' + app.archetype.vertex[archetypeIndex].name,
-					class: 'archetype-icon',
-					transform: `translate(15, -10)`,
-					onClick: this._onRelatedArchetypeIconClick.bind(this, parseInt(archetypeIndex)), // TODO when icon == null can not click on item
-				}),
-				// line
-				DOM.s('line', {
-					x1: 30,
-					y1: -5,
-					x2: 36,
-					y2: -5,
-				}),
-			]));
-
-			archetypeIconOrder++;
-		}
-
 		// vertex list
 		this._vertexListComponent = new GroupVertexList(this);
 
@@ -344,7 +299,13 @@ class Group extends Node {
 			class: 'node group',
 			'data-id': this.id,
 		}, [
-			svg,
+			// related archetypes
+			DOM.s('svg', {
+				height: this._relatedArchetypeListComponent.size.height,
+				width: 46,
+			}, [
+				this._relatedArchetypeListComponent.render(),
+			]),
 			// symbol
 			DOM.h('span', {
 				class: 'group-symbol',
diff --git a/sources/src/main/webapp/js/components/node.js b/sources/src/main/webapp/js/components/node.js
index 9ebd55a..b3fc913 100644
--- a/sources/src/main/webapp/js/components/node.js
+++ b/sources/src/main/webapp/js/components/node.js
@@ -18,6 +18,7 @@ class Node {
 
 		// components
 		this._symbolListComponent = new NodeSymbolList;
+		this._relatedArchetypeListComponent = new NodeRelatedArchetypeList(this);
 
 		this._position = null;
 		this._size = null;
@@ -33,8 +34,6 @@ class Node {
 		this._isProvidedNeighboursHighlighted = false;
 		this._isArchetypeNeighboursHighlighted = false;
 		this._isNeighbourIconsDisplayed = false;
-
-		this._relatedArchetypeMap = {};
 	}
 
 	/**
@@ -142,7 +141,7 @@ class Node {
 	 * @returns {object} Map with archetype indexes as keys and counters of their instances as values.
 	 */
 	get relatedArchetypeMap() {
-		return this._relatedArchetypeMap;
+		return this._relatedArchetypeListComponent.map;
 	}
 
 	/**
@@ -508,7 +507,7 @@ class Node {
 	 * @param {integer} archetypeIndex Index of the vertex archetype.
 	 * @param {MouseEvent} e Click event.
 	 */
-	_onRelatedArchetypeIconClick(archetypeIndex, e) {
+	onRelatedArchetypeIconClick(archetypeIndex, e) {
 		e.stopPropagation();
 
 		this.isArchetypeNeighboursHighlighted = !this.isHighlighted;
diff --git a/sources/src/main/webapp/js/components/nodeProxy.js b/sources/src/main/webapp/js/components/nodeProxy.js
index e177195..3339003 100644
--- a/sources/src/main/webapp/js/components/nodeProxy.js
+++ b/sources/src/main/webapp/js/components/nodeProxy.js
@@ -100,7 +100,7 @@ class NodeProxy {
 			edgeOffsetY = 0;
 		}
 
-		let archetypeList = Object.keys(this._node.relatedArchetypeMap).map(archetypeIndex => parseInt(archetypeIndex));
+		let archetypeList = Array.from(this._node.relatedArchetypeMap.keys());
 		let archetypeIconOrder;
 
 		// redraw dependent edges
diff --git a/sources/src/main/webapp/js/components/nodeRelatedArchetypeList.js b/sources/src/main/webapp/js/components/nodeRelatedArchetypeList.js
new file mode 100644
index 0000000..2d2603d
--- /dev/null
+++ b/sources/src/main/webapp/js/components/nodeRelatedArchetypeList.js
@@ -0,0 +1,111 @@
+/**
+ * Class representing list of related archetypes displayed next to a node to mark its relation to another, excluded node.
+ * @see Node
+ */
+class NodeRelatedArchetypeList {
+	/**
+	 * @constructor
+	 */
+	constructor(node) {
+		this._node = node;
+		this._map = new Map;
+		this._iconSize = 20;
+	}
+
+	get map() {
+		return this._map;
+	}
+
+	get size() {
+		return new Dimensions(
+			this._node.isExcluded === true ? this._iconSize : (this._map.size * this._iconSize),
+			this._node.isExcluded === true ? (this._map.size * this._iconSize) : this._iconSize,
+		);
+	}
+
+	add(key, value = 1) {
+		let currentValue;
+		if (this._map.has(key) === true) {
+			currentValue = this._map.get(key);
+		} else {
+			currentValue = 0;
+		}
+
+		this._map.set(key, currentValue + value);
+		
+		this._renderIcons();
+	}
+
+	remove(key, value = 1) {
+		let currentValue;
+		if (this._map.has(key) === true) {
+			currentValue = this._map.get(key);
+		} else {
+			currentValue = 0;
+		}
+
+		this._map.set(key, currentValue - value);
+
+		this._renderIcons();
+	}
+
+	render() {
+		if (this._node.isExcluded === true) {
+			this._rootElement = DOM.s('g', {
+				transform: 'translate(10, 15)',
+			});
+		} else {
+			this._rootElement = DOM.s('g', {
+				transform: `translate(${this._node.size.width}, 0)`,
+			});
+		}
+
+		this._renderIcons();
+
+		return this._rootElement;
+	}
+	
+	_renderIcons() {
+		if (Utils.isUndefined(this._rootElement)) return;
+		
+		this._rootElement.innerHTML = '';
+
+		let iconOrder = 0;
+		this._map.forEach((value, key) => {
+			if (this._node.isExcluded === true) {
+				this._rootElement.appendChild(DOM.s('g', {
+					class: 'related-archetype',
+					transform: `translate(0, ${iconOrder * this._iconSize})`,
+				}, [
+					// counter
+					DOM.s('text', {}, [
+						DOM.t(value),
+					]),
+					// icon
+					DOM.s('use', {
+						href: '#vertexArchetypeIcon-' + app.archetype.vertex[key].name,
+						class: 'archetype-icon',
+						transform: `translate(15, -10)`,
+						onClick: this._node.onRelatedArchetypeIconClick.bind(this._node, key), // TODO: when icon == null can not click on item
+					}),
+					// line
+					DOM.s('line', {
+						x1: 30,
+						y1: -5,
+						x2: 36,
+						y2: -5,
+					}),
+				]));
+			} else {
+				this._rootElement.appendChild(DOM.s('use', {
+					href: '#vertexArchetypeIcon-' + app.archetype.vertex[key].name,
+					class: 'archetype-icon',
+					transform: `translate(${iconOrder * this._iconSize}, 8)`,
+					onClick: this._node.onRelatedArchetypeIconClick.bind(this._node, key), // TODO: when icon == null can not click on item
+				}));
+			}
+
+			iconOrder++;
+		});
+	}
+}
diff --git a/sources/src/main/webapp/js/components/vertex.js b/sources/src/main/webapp/js/components/vertex.js
index 6ef7821..0c94e15 100644
--- a/sources/src/main/webapp/js/components/vertex.js
+++ b/sources/src/main/webapp/js/components/vertex.js
@@ -12,7 +12,6 @@ class Vertex extends Node {
 		// constants
 		this._oneCharacterWidth = 8.3;	// approximate width (in pixels) of one character using Consolas at 15px font size
 		this._minimumWidth = 200;
-		this._relatedArchetypeIconWidth = 20;
 
 		// properties
 		/** @prop {integer} archetype Identifier of the vertex archetype. */
@@ -107,11 +106,7 @@ class Vertex extends Node {
 	 * @param {integer} archetypeIndex Index of the vertex archetype.
 	 */
 	incrementRelatedArchetype(archetypeIndex) {
-		if (this.relatedArchetypeMap.hasOwnProperty(archetypeIndex) === false) {
-			this.relatedArchetypeMap[archetypeIndex] = 0;
-		}
-
-		this.relatedArchetypeMap[archetypeIndex]++;
+		this._relatedArchetypeListComponent.add(archetypeIndex);
 	}
 
 	/**
@@ -231,7 +226,7 @@ class Vertex extends Node {
 			onMouseDown: this._onNodeMouseDown.bind(this),
 		}, [
 			DOM.s('rect', {
-				width: this.size.width + Object.keys(this.relatedArchetypeMap).length * this._relatedArchetypeIconWidth,
+				width: this.size.width + this._relatedArchetypeListComponent.size.width,
 				height: this.size.height,
 				x: 1,
 				y: 1,
@@ -253,26 +248,10 @@ class Vertex extends Node {
 			]),
 			// symbol list
 			this._symbolListComponent.render(),
+			// related archetype list
+			this._relatedArchetypeListComponent.render(),
 		]);
 
-		// related archetype icons
-		const relatedArchetypeListContainer = DOM.s('g', {
-			transform: `translate(${this.size.width}, 0)`,
-		});
-		this._rootElement.appendChild(relatedArchetypeListContainer);
-
-		let archetypeIconOrder = 0;
-		for (let archetypeIndex in this.relatedArchetypeMap) {
-			relatedArchetypeListContainer.appendChild(DOM.s('use', {
-				href: '#vertexArchetypeIcon-' + app.archetype.vertex[archetypeIndex].name,
-				class: 'archetype-icon',
-				transform: `translate(${archetypeIconOrder * this._relatedArchetypeIconWidth}, 8)`,
-				onClick: this._onRelatedArchetypeIconClick.bind(this, parseInt(archetypeIndex)), // TODO when icon == null can not click on item
-			}));
-
-			archetypeIconOrder++;
-		}
-
 		return this._rootElement;
 	}
 
@@ -280,53 +259,18 @@ class Vertex extends Node {
 	 * @returns {HTMLElement} HTML DOM element.
 	 */
 	_renderExcluded() {
-		const svg = DOM.s('svg', {
-			xmlns: 'http://www.w3.org/2000/svg',
-			height: 60,
-			width: 46,
-		});
-
-		// related archetypes
-		const relatedArchetypesGroup = DOM.s('g', {
-			transform: 'translate(10, 15)',
-		});
-		svg.appendChild(relatedArchetypesGroup);
-
-		let archetypeIconOrder = 0;
-		for (let archetypeIndex in this.relatedArchetypeMap) {
-			relatedArchetypesGroup.appendChild(DOM.s('g', {
-				class: 'related-archetype',
-				transform: `translate(0, ${archetypeIconOrder * 20})`,
-			}, [
-				// counter
-				DOM.s('text', {}, [
-					DOM.t(this.relatedArchetypeMap[archetypeIndex]),
-				]),
-				// icon
-				DOM.s('use', {
-					href: '#vertexArchetypeIcon-' + app.archetype.vertex[archetypeIndex].name,
-					class: 'archetype-icon',
-					transform: `translate(15, -10)`,
-					onClick: this._onRelatedArchetypeIconClick.bind(this, parseInt(archetypeIndex)), // TODO when icon == null can not click on item
-				}),
-				// line
-				DOM.s('line', {
-					x1: 30,
-					y1: -5,
-					x2: 36,
-					y2: -5,
-				}),
-			]));
-
-			archetypeIconOrder++;
-		}
-
 		// root
 		this._rootElement = DOM.h('li', {
 			class: 'node vertex',
 			'data-id': this.id,
 		}, [
-			svg,
+			// related archetypes
+			DOM.s('svg', {
+				height: this._relatedArchetypeListComponent.size.height,
+				width: 46,
+			}, [
+				this._relatedArchetypeListComponent.render(),
+			]),
 			// name
 			DOM.h('div', {
 				class: 'vertex-name',
diff --git a/sources/src/main/webapp/showGraph.jsp b/sources/src/main/webapp/showGraph.jsp
index a701556..b277a0e 100644
--- a/sources/src/main/webapp/showGraph.jsp
+++ b/sources/src/main/webapp/showGraph.jsp
@@ -50,6 +50,7 @@
 		<script src="js/components/loginPopup.js"></script>
 		<script src="js/components/minimap.js"></script>
 		<script src="js/components/navbar.js"></script>
+		<script src="js/components/nodeRelatedArchetypeList.js"></script>
 		<script src="js/components/nodeSymbolList.js"></script>
 		<script src="js/components/registerPopup.js"></script>
 		<script src="js/components/saveDiagramModalWindow.js"></script>
