var util = require('../../util'); var Node = require('../Node'); var Edge = require('../Edge'); var Hammer = require('../../module/hammer'); var hammerUtil = require('../../hammerUtil'); /** * clears the toolbar div element of children * * @private */ exports._clearManipulatorBar = function() { this._recursiveDOMDelete(this.manipulationDiv); this.manipulationDOM = {}; this._cleanManipulatorHammers(); this._manipulationReleaseOverload = function () {}; delete this.body.sectors['support']['nodes']['targetNode']; delete this.body.sectors['support']['nodes']['targetViaNode']; this.controlNodesActive = false; this.freezeSimulationEnabled = false; }; exports._cleanManipulatorHammers = function() { // clean hammer bindings if (this.manipulationHammers.length != 0) { for (var i = 0; i < this.manipulationHammers.length; i++) { this.manipulationHammers[i].destroy(); } this.manipulationHammers = []; } }; /** * Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore * these functions to their original functionality, we saved them in this.cachedFunctions. * This function restores these functions to their original function. * * @private */ exports._restoreOverloadedFunctions = function() { for (var functionName in this.cachedFunctions) { if (this.cachedFunctions.hasOwnProperty(functionName)) { this[functionName] = this.cachedFunctions[functionName]; delete this.cachedFunctions[functionName]; } } }; /** * Enable or disable edit-mode. * * @private */ exports._toggleEditMode = function() { this.editMode = !this.editMode; var toolbar = this.manipulationDiv; var closeDiv = this.closeDiv; var editModeDiv = this.editModeDiv; if (this.editMode == true) { toolbar.style.display="block"; closeDiv.style.display="block"; editModeDiv.style.display="none"; this._bindHammerToDiv(closeDiv,'_toggleEditMode'); } else { toolbar.style.display="none"; closeDiv.style.display="none"; editModeDiv.style.display="block"; } this._createManipulatorBar() }; /** * main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar. * * @private */ exports._createManipulatorBar = function() { // remove bound functions if (this.boundFunction) { this.off('select', this.boundFunction); } this._cleanManipulatorHammers(); var locale = this.constants.locales[this.constants.locale]; if (this.edgeBeingEdited !== undefined) { this.edgeBeingEdited._disableControlNodes(); this.edgeBeingEdited = undefined; this.selectedControlNode = null; this.controlNodesActive = false; this._redraw(); } // restore overloaded functions this._restoreOverloadedFunctions(); // resume calculation this.freezeSimulation(false); // reset global variables this.blockConnectingEdgeSelection = false; this.forceAppendSelection = false; this.manipulationDOM = {}; if (this.editMode == true) { while (this.manipulationDiv.hasChildNodes()) { this.manipulationDiv.removeChild(this.manipulationDiv.firstChild); } this.manipulationDOM['addNodeSpan'] = document.createElement('div'); this.manipulationDOM['addNodeSpan'].className = 'network-manipulationUI add'; this.manipulationDOM['addNodeLabelSpan'] = document.createElement('div'); this.manipulationDOM['addNodeLabelSpan'].className = 'network-manipulationLabel'; this.manipulationDOM['addNodeLabelSpan'].innerHTML = locale['addNode']; this.manipulationDOM['addNodeSpan'].appendChild(this.manipulationDOM['addNodeLabelSpan']); this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div'); this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine'; this.manipulationDOM['addEdgeSpan'] = document.createElement('div'); this.manipulationDOM['addEdgeSpan'].className = 'network-manipulationUI connect'; this.manipulationDOM['addEdgeLabelSpan'] = document.createElement('div'); this.manipulationDOM['addEdgeLabelSpan'].className = 'network-manipulationLabel'; this.manipulationDOM['addEdgeLabelSpan'].innerHTML = locale['addEdge']; this.manipulationDOM['addEdgeSpan'].appendChild(this.manipulationDOM['addEdgeLabelSpan']); this.manipulationDiv.appendChild(this.manipulationDOM['addNodeSpan']); this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']); this.manipulationDiv.appendChild(this.manipulationDOM['addEdgeSpan']); if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) { this.manipulationDOM['seperatorLineDiv2'] = document.createElement('div'); this.manipulationDOM['seperatorLineDiv2'].className = 'network-seperatorLine'; this.manipulationDOM['editNodeSpan'] = document.createElement('div'); this.manipulationDOM['editNodeSpan'].className = 'network-manipulationUI edit'; this.manipulationDOM['editNodeLabelSpan'] = document.createElement('div'); this.manipulationDOM['editNodeLabelSpan'].className = 'network-manipulationLabel'; this.manipulationDOM['editNodeLabelSpan'].innerHTML = locale['editNode']; this.manipulationDOM['editNodeSpan'].appendChild(this.manipulationDOM['editNodeLabelSpan']); this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv2']); this.manipulationDiv.appendChild(this.manipulationDOM['editNodeSpan']); } else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) { this.manipulationDOM['seperatorLineDiv3'] = document.createElement('div'); this.manipulationDOM['seperatorLineDiv3'].className = 'network-seperatorLine'; this.manipulationDOM['editEdgeSpan'] = document.createElement('div'); this.manipulationDOM['editEdgeSpan'].className = 'network-manipulationUI edit'; this.manipulationDOM['editEdgeLabelSpan'] = document.createElement('div'); this.manipulationDOM['editEdgeLabelSpan'].className = 'network-manipulationLabel'; this.manipulationDOM['editEdgeLabelSpan'].innerHTML = locale['editEdge']; this.manipulationDOM['editEdgeSpan'].appendChild(this.manipulationDOM['editEdgeLabelSpan']); this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv3']); this.manipulationDiv.appendChild(this.manipulationDOM['editEdgeSpan']); } if (this._selectionIsEmpty() == false) { this.manipulationDOM['seperatorLineDiv4'] = document.createElement('div'); this.manipulationDOM['seperatorLineDiv4'].className = 'network-seperatorLine'; this.manipulationDOM['deleteSpan'] = document.createElement('div'); this.manipulationDOM['deleteSpan'].className = 'network-manipulationUI delete'; this.manipulationDOM['deleteLabelSpan'] = document.createElement('div'); this.manipulationDOM['deleteLabelSpan'].className = 'network-manipulationLabel'; this.manipulationDOM['deleteLabelSpan'].innerHTML = locale['del']; this.manipulationDOM['deleteSpan'].appendChild(this.manipulationDOM['deleteLabelSpan']); this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv4']); this.manipulationDiv.appendChild(this.manipulationDOM['deleteSpan']); } // bind the icons this._bindHammerToDiv(this.manipulationDOM['addNodeSpan'],'_createAddNodeToolbar'); this._bindHammerToDiv(this.manipulationDOM['addEdgeSpan'],'_createAddEdgeToolbar'); this._bindHammerToDiv(this.closeDiv,'_toggleEditMode'); if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) { this._bindHammerToDiv(this.manipulationDOM['editNodeSpan'],'_editNode'); } else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) { this._bindHammerToDiv(this.manipulationDOM['editEdgeSpan'],'_createEditEdgeToolbar'); } if (this._selectionIsEmpty() == false) { this._bindHammerToDiv(this.manipulationDOM['deleteSpan'],'_deleteSelected'); } var me = this; this.boundFunction = me._createManipulatorBar; this.on('select', this.boundFunction); } else { while (this.editModeDiv.hasChildNodes()) { this.editModeDiv.removeChild(this.editModeDiv.firstChild); } this.manipulationDOM['editModeSpan'] = document.createElement('div'); this.manipulationDOM['editModeSpan'].className = 'network-manipulationUI edit editmode'; this.manipulationDOM['editModeLabelSpan'] = document.createElement('div'); this.manipulationDOM['editModeLabelSpan'].className = 'network-manipulationLabel'; this.manipulationDOM['editModeLabelSpan'].innerHTML = locale['edit']; this.manipulationDOM['editModeSpan'].appendChild(this.manipulationDOM['editModeLabelSpan']); this.editModeDiv.appendChild(this.manipulationDOM['editModeSpan']); this._bindHammerToDiv(this.manipulationDOM['editModeSpan'],'_toggleEditMode'); } }; exports._bindHammerToDiv = function(domElement, funct) { var hammer = new Hammer(domElement, {}); hammerUtil.onTouch(hammer, this[funct].bind(this)); this.manipulationHammers.push(hammer); }; /** * Create the toolbar for adding Nodes * * @private */ exports._createAddNodeToolbar = function() { // clear the toolbar this._clearManipulatorBar(); if (this.boundFunction) { this.off('select', this.boundFunction); } var locale = this.constants.locales[this.constants.locale]; this.manipulationDOM = {}; this.manipulationDOM['backSpan'] = document.createElement('div'); this.manipulationDOM['backSpan'].className = 'network-manipulationUI back'; this.manipulationDOM['backLabelSpan'] = document.createElement('div'); this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel'; this.manipulationDOM['backLabelSpan'].innerHTML = locale['back']; this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']); this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div'); this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine'; this.manipulationDOM['descriptionSpan'] = document.createElement('div'); this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none'; this.manipulationDOM['descriptionLabelSpan'] = document.createElement('div'); this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel'; this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['addDescription']; this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']); this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']); this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']); this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']); // bind the icon this._bindHammerToDiv(this.manipulationDOM['backSpan'],'_createManipulatorBar'); // we use the boundFunction so we can reference it when we unbind it from the "select" event. var me = this; this.boundFunction = me._addNode; this.on('select', this.boundFunction); }; /** * create the toolbar to connect nodes * * @private */ exports._createAddEdgeToolbar = function() { // clear the toolbar this._clearManipulatorBar(); this._unselectAll(true); this.freezeSimulationEnabled = true; if (this.boundFunction) { this.off('select', this.boundFunction); } var locale = this.constants.locales[this.constants.locale]; this.forceAppendSelection = false; this.blockConnectingEdgeSelection = true; this.manipulationDOM = {}; this.manipulationDOM['backSpan'] = document.createElement('div'); this.manipulationDOM['backSpan'].className = 'network-manipulationUI back'; this.manipulationDOM['backLabelSpan'] = document.createElement('div'); this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel'; this.manipulationDOM['backLabelSpan'].innerHTML = locale['back']; this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']); this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div'); this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine'; this.manipulationDOM['descriptionSpan'] = document.createElement('div'); this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none'; this.manipulationDOM['descriptionLabelSpan'] = document.createElement('div'); this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel'; this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['edgeDescription']; this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']); this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']); this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']); this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']); // bind the icon this._bindHammerToDiv(this.manipulationDOM['backSpan'],'_createManipulatorBar'); // we use the boundFunction so we can reference it when we unbind it from the "select" event. var me = this; this.boundFunction = me._handleConnect; this.on('select', this.boundFunction); // temporarily overload functions this.cachedFunctions["_handleTouch"] = this._handleTouch; this.cachedFunctions["_manipulationReleaseOverload"] = this._manipulationReleaseOverload; this.cachedFunctions["_handleDragStart"] = this._handleDragStart; this.cachedFunctions["_handleDragEnd"] = this._handleDragEnd; this.cachedFunctions["_handleOnHold"] = this._handleOnHold; this._handleTouch = this._handleConnect; this._manipulationReleaseOverload = function () {}; this._handleOnHold = function () {}; this._handleDragStart = function () {}; this._handleDragEnd = this._finishConnect; // redraw to show the unselect this._redraw(); }; /** * create the toolbar to edit edges * * @private */ exports._createEditEdgeToolbar = function() { // clear the toolbar this._clearManipulatorBar(); this.controlNodesActive = true; if (this.boundFunction) { this.off('select', this.boundFunction); } this.edgeBeingEdited = this._getSelectedEdge(); this.edgeBeingEdited._enableControlNodes(); var locale = this.constants.locales[this.constants.locale]; this.manipulationDOM = {}; this.manipulationDOM['backSpan'] = document.createElement('div'); this.manipulationDOM['backSpan'].className = 'network-manipulationUI back'; this.manipulationDOM['backLabelSpan'] = document.createElement('div'); this.manipulationDOM['backLabelSpan'].className = 'network-manipulationLabel'; this.manipulationDOM['backLabelSpan'].innerHTML = locale['back']; this.manipulationDOM['backSpan'].appendChild(this.manipulationDOM['backLabelSpan']); this.manipulationDOM['seperatorLineDiv1'] = document.createElement('div'); this.manipulationDOM['seperatorLineDiv1'].className = 'network-seperatorLine'; this.manipulationDOM['descriptionSpan'] = document.createElement('div'); this.manipulationDOM['descriptionSpan'].className = 'network-manipulationUI none'; this.manipulationDOM['descriptionLabelSpan'] = document.createElement('div'); this.manipulationDOM['descriptionLabelSpan'].className = 'network-manipulationLabel'; this.manipulationDOM['descriptionLabelSpan'].innerHTML = locale['editEdgeDescription']; this.manipulationDOM['descriptionSpan'].appendChild(this.manipulationDOM['descriptionLabelSpan']); this.manipulationDiv.appendChild(this.manipulationDOM['backSpan']); this.manipulationDiv.appendChild(this.manipulationDOM['seperatorLineDiv1']); this.manipulationDiv.appendChild(this.manipulationDOM['descriptionSpan']); // bind the icon this._bindHammerToDiv(this.manipulationDOM['backSpan'],'_createManipulatorBar'); // temporarily overload functions this.cachedFunctions["_handleTouch"] = this._handleTouch; this.cachedFunctions["_manipulationReleaseOverload"] = this._manipulationReleaseOverload; this.cachedFunctions["_handleTap"] = this._handleTap; this.cachedFunctions["_handleDragStart"] = this._handleDragStart; this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag; this._handleTouch = this._selectControlNode; this._handleTap = function () {}; this._handleOnDrag = this._controlNodeDrag; this._handleDragStart = function () {} this._manipulationReleaseOverload = this._releaseControlNode; // redraw to show the unselect this._redraw(); }; /** * the function bound to the selection event. It checks if you want to connect a cluster and changes the description * to walk the user through the process. * * @private */ exports._selectControlNode = function(pointer) { this.edgeBeingEdited.controlNodes.from.unselect(); this.edgeBeingEdited.controlNodes.to.unselect(); this.selectedControlNode = this.edgeBeingEdited._getSelectedControlNode(this._XconvertDOMtoCanvas(pointer.x),this._YconvertDOMtoCanvas(pointer.y)); if (this.selectedControlNode !== null) { this.selectedControlNode.select(); this.freezeSimulation(true); } this._redraw(); }; /** * the function bound to the selection event. It checks if you want to connect a cluster and changes the description * to walk the user through the process. * * @private */ exports._controlNodeDrag = function(event) { var pointer = this._getPointer(event.center); if (this.selectedControlNode !== null && this.selectedControlNode !== undefined) { this.selectedControlNode.x = this._XconvertDOMtoCanvas(pointer.x); this.selectedControlNode.y = this._YconvertDOMtoCanvas(pointer.y); } this._redraw(); }; /** * * @param pointer * @private */ exports._releaseControlNode = function(pointer) { var newNode = this._getNodeAt(pointer); if (newNode !== null) { if (this.edgeBeingEdited.controlNodes.from.selected == true) { this.edgeBeingEdited._restoreControlNodes(); this._editEdge(newNode.id, this.edgeBeingEdited.to.id); this.edgeBeingEdited.controlNodes.from.unselect(); } if (this.edgeBeingEdited.controlNodes.to.selected == true) { this.edgeBeingEdited._restoreControlNodes(); this._editEdge(this.edgeBeingEdited.from.id, newNode.id); this.edgeBeingEdited.controlNodes.to.unselect(); } } else { this.edgeBeingEdited._restoreControlNodes(); } this.freezeSimulation(false); this._redraw(); }; /** * the function bound to the selection event. It checks if you want to connect a cluster and changes the description * to walk the user through the process. * * @private */ exports._handleConnect = function(pointer) { if (this._getSelectedNodeCount() == 0) { var node = this._getNodeAt(pointer); if (node != null) { if (this.isCluster(node.id) == true) { alert(this.constants.locales[this.constants.locale]['createEdgeError']) } else { this._selectObject(node,false); var supportNodes = this.body.sectors['support']['nodes']; // create a node the temporary line can look at supportNodes['targetNode'] = this.body.functions.createNode({id:'targetNode'}); var targetNode = supportNodes['targetNode']; targetNode.x = node.x; targetNode.y = node.y; // create a temporary edge this.body.edges['connectionEdge'] = this.body.functions.createEdge({id:"connectionEdge",from:node.id,to:targetNode.id}); var connectionEdge = this.body.edges['connectionEdge']; connectionEdge.from = node; connectionEdge.connected = true; connectionEdge.options.smoothCurves = {enabled: true, dynamic: false, type: "continuous", roundness: 0.5 }; connectionEdge.selected = true; connectionEdge.to = targetNode; this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag; var me = this; this._handleOnDrag = function(event) { var pointer = me._getPointer(event.center); var connectionEdge = me.body.edges['connectionEdge']; connectionEdge.to.x = me._XconvertDOMtoCanvas(pointer.x); connectionEdge.to.y = me._YconvertDOMtoCanvas(pointer.y); me._redraw(); }; } } } }; exports._finishConnect = function(event) { if (this._getSelectedNodeCount() == 1) { var pointer = this._getPointer(event.center); // restore the drag function this._handleOnDrag = this.cachedFunctions["_handleOnDrag"]; delete this.cachedFunctions["_handleOnDrag"]; // remember the edge id var connectFromId = this.body.edges['connectionEdge'].fromId; // remove the temporary nodes and edge delete this.body.edges['connectionEdge']; delete this.body.sectors['support']['nodes']['targetNode']; delete this.body.sectors['support']['nodes']['targetViaNode']; var node = this._getNodeAt(pointer); if (node != null) { if (this.isCluster(node.id) === true) { alert(this.constants.locales[this.constants.locale]["createEdgeError"]) } else { this._createEdge(connectFromId,node.id); this._createManipulatorBar(); } } this._unselectAll(); } }; /** * Adds a node on the specified location */ exports._addNode = function() { if (this._selectionIsEmpty() && this.editMode == true) { var positionObject = this._pointerToPositionObject(this.pointerPosition); var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",allowedToMoveX:true,allowedToMoveY:true}; if (this.triggerFunctions.add) { if (this.triggerFunctions.add.length == 2) { var me = this; this.triggerFunctions.add(defaultData, function(finalizedData) { me.body.data.nodes.add(finalizedData); me._createManipulatorBar(); me.moving = true; me.start(); }); } else { throw new Error('The function for add does not support two arguments (data,callback)'); this._createManipulatorBar(); this.moving = true; this.start(); } } else { this.body.data.nodes.add(defaultData); this._createManipulatorBar(); this.moving = true; this.start(); } } }; /** * connect two nodes with a new edge. * * @private */ exports._createEdge = function(sourceNodeId,targetNodeId) { if (this.editMode == true) { var defaultData = {from:sourceNodeId, to:targetNodeId}; if (this.triggerFunctions.connect) { if (this.triggerFunctions.connect.length == 2) { var me = this; this.triggerFunctions.connect(defaultData, function(finalizedData) { me.body.data.edges.add(finalizedData); me.moving = true; me.start(); }); } else { throw new Error('The function for connect does not support two arguments (data,callback)'); this.moving = true; this.start(); } } else { this.body.data.edges.add(defaultData); this.moving = true; this.start(); } } }; /** * connect two nodes with a new edge. * * @private */ exports._editEdge = function(sourceNodeId,targetNodeId) { if (this.editMode == true) { var defaultData = {id: this.edgeBeingEdited.id, from:sourceNodeId, to:targetNodeId}; console.log(defaultData); if (this.triggerFunctions.editEdge) { if (this.triggerFunctions.editEdge.length == 2) { var me = this; this.triggerFunctions.editEdge(defaultData, function(finalizedData) { me.body.data.edges.update(finalizedData); me.moving = true; me.start(); }); } else { throw new Error('The function for edit does not support two arguments (data, callback)'); this.moving = true; this.start(); } } else { this.body.data.edges.update(defaultData); this.moving = true; this.start(); } } }; /** * Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color. * * @private */ exports._editNode = function() { if (this.triggerFunctions.edit && this.editMode == true) { var node = this._getSelectedNode(); var data = {id:node.id, label: node.label, group: node.options.group, shape: node.options.shape, color: { background:node.options.color.background, border:node.options.color.border, highlight: { background:node.options.color.highlight.background, border:node.options.color.highlight.border } }}; if (this.triggerFunctions.edit.length == 2) { var me = this; this.triggerFunctions.edit(data, function (finalizedData) { me.body.data.nodes.update(finalizedData); me._createManipulatorBar(); me.moving = true; me.start(); }); } else { throw new Error('The function for edit does not support two arguments (data, callback)'); } } else { throw new Error('No edit function has been bound to this button'); } }; /** * delete everything in the selection * * @private */ exports._deleteSelected = function() { if (!this._selectionIsEmpty() && this.editMode == true) { if (!this._clusterInSelection()) { var selectedNodes = this.getSelectedNodes(); var selectedEdges = this.getSelectedEdges(); if (this.triggerFunctions.del) { var me = this; var data = {nodes: selectedNodes, edges: selectedEdges}; if (this.triggerFunctions.del.length == 2) { this.triggerFunctions.del(data, function (finalizedData) { me.body.data.edges.remove(finalizedData.edges); me.body.data.nodes.remove(finalizedData.nodes); me._unselectAll(); me.moving = true; me.start(); }); } else { throw new Error('The function for delete does not support two arguments (data, callback)') } } else { this.body.data.edges.remove(selectedEdges); this.body.data.nodes.remove(selectedNodes); this._unselectAll(); this.moving = true; this.start(); } } else { alert(this.constants.locales[this.constants.locale]["deleteClusterError"]); } } };