bug fix vnfd composer; minor ui fix
[osm/LW-UI.git] / static / TopologyComposer / js / model_graph_editor.js
1 /*
2 Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
3 Copyright 2018 EveryUP srl
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16 */
17 if (typeof TCD3 === 'undefined') {
18 var TCD3 = {};
19 }
20
21 TCD3.ModelGraphEditor = (function () {
22 'use strict';
23
24 var DEBUG = true;
25 var SHIFT_BUTTON = 16;
26 var IMAGE_PATH = "/static/assets/img/";
27
28
29 ModelGraphEditor.prototype = new TCD3.GraphEditor();
30 ModelGraphEditor.prototype.constructor = ModelGraphEditor;
31 ModelGraphEditor.prototype.parent = TCD3.GraphEditor.prototype;
32
33 /**
34 * Constructor
35 */
36 function ModelGraphEditor(args) {
37
38 log("Constructor");
39
40 }
41
42
43 ModelGraphEditor.prototype.init = function (args) {
44 this.parent.init.call(this, args);
45 var self = this;
46 this.desc_id = args.desc_id || undefined; //TODO remove it
47
48 this.type_property = {};
49 this.type_property["unrecognized"] = args.gui_properties["nodes"]["default"];
50
51 this._edit_mode = args.edit_mode || false;
52
53 Object.keys(args.gui_properties["nodes"]).forEach(function (key, index) {
54
55 this.type_property[key] = args.gui_properties["nodes"][key];
56
57 this.type_property[key]["shape"] = this.parent.get_d3_symbol(this.type_property[key]["shape"]);
58 if (this.type_property[key]["image"] !== undefined) {
59 this.type_property[key]["image"] = IMAGE_PATH + this.type_property[key]["image"];
60 }
61
62 }, this);
63
64 if (args.gui_properties["edges"]) {
65 this.type_property_link = args.gui_properties["edges"];
66 var link_types = ['unrecognized'].concat(Object.keys(self.type_property_link))
67 this.defs.selectAll("marker")
68 .data(link_types)
69 .enter()
70 .append("svg:marker") // This section adds in the arrows
71 .attr("id", function (d) {
72 return d;
73 })
74 .attr("viewBox", "-5 -5 10 10")
75 .attr("refX", 13) /*must be smarter way to calculate shift*/
76 .attr("refY", 0)
77 .attr("markerUnits", "userSpaceOnUse")
78 .attr("markerWidth", 12)
79 .attr("markerHeight", 12)
80 .attr("orient", "auto")
81 .append("path")
82 .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z")
83 .attr('fill', function (d) {
84 return self.type_property_link[d].color;
85 });
86 }
87
88 this.customBehavioursOnEvents = args.behaviorsOnEvents || undefined;
89
90
91 var data_url = args.data_url || undefined;
92 if (!args.graph_data && args.data_url) {
93 d3.json(data_url, function (error, data) {
94 //console.log(JSON.stringify(data))
95 self.d3_graph.nodes = data.vertices;
96 self.d3_graph.links = data.edges;
97 self.d3_graph.graph_parameters = data.graph_parameters;
98 self.model = data.model;
99 self.refreshGraphParameters(self.d3_graph.graph_parameters);
100 self.refresh();
101 self.startForce();
102 //if(args.filter_base != undefined)
103
104 setTimeout(function () {
105 self.handleForce(true);
106 self.handleFiltersParams(args.filter_base);
107 }, 500);
108
109 });
110 } else {
111 this.updateData(args)
112 }
113 }
114
115 /**
116 * Update data of the graph.
117 * @param {Object} Required. An object that specifies tha data of the new node.
118 * @returns {boolean}
119 */
120 ModelGraphEditor.prototype.updateData = function (args) {
121 this.d3_graph.nodes = args.vertices;
122 this.d3_graph.links = args.edges;
123 this.d3_graph.graph_parameters = args.graph_parameters;
124 this.model = args.model;
125 this.refreshGraphParameters(this.d3_graph.graph_parameters);
126 this.cleanAll();
127 this.refresh();
128 this.startForce();
129 this.handleForce(this.forceSimulationActive);
130 //this.force.restart();
131 //if(args.filter_base != undefined)
132
133 if(args.filter_base){
134 var self = this;
135 setTimeout(function () {
136 self.handleForce(true);
137 self.handleFiltersParams(args.filter_base);
138 }, 500);
139 }
140 };
141
142 /**
143 * Add a new node to the graph.
144 * @param {Object} Required. An object that specifies tha data of the new node.
145 * @returns {boolean}
146 */
147 ModelGraphEditor.prototype.addNode = function (node, success, error) {
148 var self = this;
149 var current_layer = self.getCurrentView();
150 var node_type = node.info.type;
151 if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].addable) {
152 if (self.model.layer[current_layer].nodes[node_type].addable.callback) {
153 console.log(self.model.callback)
154 var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].addable.callback].class;
155 var controller = new TCD3.OsmController();
156 controller[self.model.layer[current_layer].nodes[node_type].addable.callback](self, node, function (result) {
157 console.log(result)
158 console.log(node)
159 self.updateData(result);
160 // self.parent.addNode.call(self, node);
161 success && success();
162 }, error);
163
164 } else {
165
166 log('addNode: callback undefined in model spec.');
167 error && error("You can't add a " + node.info.type + ", callback undefined.");
168 }
169 } else {
170 //FIXME Error handling????
171 log("You can't add a " + node.info.type + " in a current layer " + current_layer);
172 error && error("You can't add a " + node.info.type + " in a current layer " + current_layer);
173 }
174 };
175
176
177 /**
178 * Update the data properties of the node
179 * @param {Object} Required. An object that specifies tha data of the node.
180 * @returns {boolean}
181 */
182 ModelGraphEditor.prototype.updateDataNode = function (node, args, success, error) {
183 console.log(node);
184 var self = this;
185 var controller = new TCD3.OsmController();
186 controller.updateNode(this,node, args, function(result){
187 self.updateData(result);
188 success && success();
189 }, error);
190 };
191
192 /**
193 * Update the data properties of the node
194 * @param {Object} Required. An object that specifies tha data of the node.
195 * @returns {boolean}
196 */
197 ModelGraphEditor.prototype.updateGraphParams = function (args, success, error) {
198 var controller = new TCD3.OsmController();
199 controller.updateGraphParams(args, function(){
200 success && success();
201 }, error);
202 };
203
204 /**
205 * Remove a node from graph and related links.
206 * @param {String} Required. Id of node to remove.
207 * @returns {boolean}
208 */
209 ModelGraphEditor.prototype.removeNode = function (node, success, error) {
210 console.log('removeNode', JSON.stringify(node))
211 var self = this;
212 var current_layer = self.getCurrentView();
213 var node_type = node.info.type;
214 if (node.info.desc_id == undefined) {
215 node.info.desc_id = self.desc_id;
216 }
217 if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].removable) {
218 if (self.model.layer[current_layer].nodes[node_type].removable.callback) {
219 var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].removable.callback].class;
220 var controller = new TCD3.OsmController();
221 controller[self.model.layer[current_layer].nodes[node_type].removable.callback](self, node, function () {
222 self.parent.removeNode.call(self, node);
223 success && success();
224 }, error);
225 } else {
226
227 log('removeNode: callback undefined in model spec.');
228 error && error("You can't remove a " + node.info.type + ", callback undefined.");
229 }
230 } else {
231 //FIXME we need to manage alert in a different way: FAILBACK
232 log("You can't remove a " + node.info.type);
233 error && error("You can't remove a " + node.info.type);
234 }
235 };
236
237 /**
238 * Add a new link to graph.
239 * @param {Object} Required. An object that specifies tha data of the new Link.
240 * @returns {boolean}
241 */
242 ModelGraphEditor.prototype.addLink = function (s, d, success, error) {
243 var self = this;
244 var source_id = s.id;
245 var target_id = d.id;
246 var source_type = s.info.type;
247 var destination_type = d.info.type;
248 var link = {
249 source: s,
250 target: d,
251 view: this.filter_parameters.link.view[0],
252 group: this.filter_parameters.link.group,
253 desc_id: this.desc_id
254 };
255 log("addLink: " + JSON.stringify(link))
256 var current_layer = self.getCurrentView()
257 if (self.model.layer[current_layer].allowed_edges && self.model.layer[current_layer].allowed_edges[source_type] && self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type]) {
258
259 if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback) {
260 var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback;
261 console.log(callback, self.model.callback)
262 var direct_edge = 'direct_edge' in self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type] ? self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type]['direct_edge'] : false;
263 link.directed_edge = direct_edge;
264 var c = self.model.callback[callback].class;
265 var controller = new TCD3.OsmController();
266 controller[callback](self, link, function () {
267 self._deselectAllNodes();
268 self.parent.addLink.call(self, link);
269 if (success)
270 success();
271 }, error);
272 } else {
273 log('addLink: callback undefined in model spec.');
274 error && error("You can't add a link, callback undefined.");
275 }
276
277 } else {
278 //FIXME we need to manage alert in a different way: FAILBACK
279 log("You can't link a " + source_type + " with a " + destination_type);
280
281 error && error("You can't link a " + source_type + " with a " + destination_type);
282 }
283 };
284
285 /**
286 * Remove a link from graph.
287 * @param {String} Required. The identifier of link to remove.
288 * @returns {boolean}
289 */
290 ModelGraphEditor.prototype.removeLink = function (link, success, error) {
291 var self = this;
292 var s = link.source;
293 var d = link.target;
294 var source_type = s.info.type;
295 var destination_type = d.info.type;
296 var current_layer = self.getCurrentView();
297 if (self.model.layer[current_layer].allowed_edges && self.model.layer[current_layer].allowed_edges[source_type] && self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type] &&
298 self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable
299 ) {
300 if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback) {
301 var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback;
302 var c = self.model.callback[callback].class;
303 var controller = new TCD3.OsmController();
304 controller[callback](self, link, function () {
305 self._deselectAllNodes();
306 self._deselectAllLinks();
307 self.parent.removeLink.call(self, link.index);
308 success && success();
309 }, error);
310 } else {
311 log('removeLink: callback undefined in model spec.');
312 error && error("You can't remove a link, callback undefined.");
313 }
314
315 } else {
316 //FIXME we need to manage alert in a different way: FAILBACK
317 log("You can't delete the link");
318 error && error("You can't delete the link");
319 }
320
321
322 };
323
324
325 ModelGraphEditor.prototype.savePositions = function (data) {
326 var vertices = {};
327 this.node.each(function (d) {
328 vertices[d.id] = {};
329 vertices[d.id]['x'] = d.x;
330 vertices[d.id]['y'] = d.y;
331 });
332 new TCD3.GraphRequests().savePositions({
333 'vertices': vertices
334 });
335
336 };
337
338 /**
339 * Internal functions
340 */
341
342 ModelGraphEditor.prototype._setupBehaviorsOnEvents = function (layer) {
343
344 var self = this;
345 var contextMenuLinksAction = [{
346 title: 'Delete Link',
347 action: function (elm, link, i) {
348 self.removeLink(link, null, showAlert);
349 },
350 edit_mode: true
351 }];
352 var contextMenuNodesAction = [
353 {
354 title: 'Delete',
355 action: function (elm, d, i) {
356 self.removeNode(d, null, showAlert);
357 },
358 edit_mode: true
359 }
360
361 ];
362 if (this.customBehavioursOnEvents) {
363 contextMenuNodesAction = contextMenuNodesAction.concat(this.customBehavioursOnEvents['behaviors'].nodes);
364 }
365
366
367 if (self.model && self.model.layer && self.model.layer[layer] && self.model.layer[layer].action && self.model.layer[layer].action.node) {
368 for (var i in self.model.layer[layer].action.node) {
369 var action = self.model.layer[layer].action.node[i]
370 contextMenuNodesAction.push({
371 title: action.title,
372 action: function (elm, d, i) {
373 var callback = action.callback;
374 var c = self.model.callback[callback].class;
375 var controller = new TCD3[c]();
376 var args = {
377 elm: elm,
378 d: d,
379 i: i
380 };
381
382 controller[callback](self, args);
383 },
384 edit_mode: (action.edit_mode !== undefined) ? action.edit_mode : undefined
385 });
386 }
387 }
388
389 this.behavioursOnEvents = {
390 'nodes': {
391 'click': function (d) {
392
393 d3.event.preventDefault();
394
395 if (self._edit_mode && self.lastKeyDown === SHIFT_BUTTON && self._selected_node !== undefined) {
396 self.addLink(self._selected_node, d, null, showAlert);
397 } else {
398 self._selectNodeExclusive(this, d);
399 }
400
401 },
402 'mouseover': function (d) {
403 self.link.style('stroke-width', function (l) {
404 if (d === l.source || d === l.target)
405 return 4;
406 else
407 return 2;
408 });
409 },
410 'mouseout': function (d) {
411 self.link.style('stroke-width', 2);
412 },
413 'contextmenu': d3.contextMenu(contextMenuNodesAction, {
414 'edit_mode': self._edit_mode,
415 'layer': layer,
416 'type_object': 'node'
417 })
418 },
419 'links': {
420 'click': function (d) {
421 self._selectLinkExclusive(this, d);
422 },
423 'dblclick': function (event) {
424
425 },
426 'mouseover': function (d) {
427 d3.select(this).style('stroke-width', 4);
428 },
429 'mouseout': function (d) {
430 if (d !== self._selected_link)
431 d3.select(this).style('stroke-width', 2);
432 },
433 'contextmenu': d3.contextMenu(contextMenuLinksAction, {
434 'edit_mode': self._edit_mode,
435 'layer': layer,
436 'type_object': 'link'
437 })
438 }
439 }
440 };
441
442 ModelGraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) {
443
444 this.parent.handleFiltersParams.call(this, filtersParams, notFireEvent);
445 if (filtersParams && filtersParams.link && filtersParams.link.view)
446 this._setupBehaviorsOnEvents(filtersParams.link.view[0]);
447 };
448
449 ModelGraphEditor.prototype.getAvailableNodes = function () {
450 log('getAvailableNodes');
451 log(this.model);
452 if (this.model && this.model.layer[this.getCurrentView()] !== undefined)
453 return this.model.layer[this.getCurrentView()].nodes;
454 return [];
455 };
456
457
458 ModelGraphEditor.prototype.getTypeProperty = function () {
459 return this.type_property;
460 };
461
462 ModelGraphEditor.prototype.getCurrentGroup = function () {
463 return this.filter_parameters.node.group[0];
464 };
465
466 ModelGraphEditor.prototype.getCurrentView = function () {
467 return this.filter_parameters.link.view[0];
468 };
469 ModelGraphEditor.prototype.getCurrentFilters = function () {
470 return this.filter_parameters;
471 };
472
473 ModelGraphEditor.prototype.getGraphParams = function () {
474 return this.d3_graph.graph_parameters;
475 };
476
477 /**
478 * Log utility
479 */
480 function log(text) {
481 if (DEBUG)
482 console.log("::ModelGraphEditor::", text);
483 }
484
485
486 return ModelGraphEditor;
487
488
489 }(this));
490
491 if (typeof module === 'object') {
492 module.exports = TCD3.ModelGraphEditor;
493 }