fix odl patches
[ccsdk/distribution.git] / dgbuilder / public / red / nodes.js
1 /**
2  * Copyright 2013 IBM Corp.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  **/
16 RED.nodes = (function() {
17
18     var node_defs = {};
19     var nodes = [];
20     var configNodes = {};
21     var links = [];
22     var defaultWorkspace;
23     var workspaces = {};
24     
25     var registry = (function() {
26         var nodeList = [];
27         var nodeSets = {};
28         var typeToId = {};
29         var nodeDefinitions = {};
30         
31         var exports = {
32             getNodeList: function() {
33                 return nodeList;
34             },
35             setNodeList: function(list) {
36                 nodeList = [];
37                 for(var i=0;i<list.length;i++) {
38                     var ns = list[i];
39                     exports.addNodeSet(ns);
40                 }
41             },
42             addNodeSet: function(ns) {
43                 ns.added = false;
44                 nodeSets[ns.id] = ns;
45                 for (var j=0;j<ns.types.length;j++) {
46                     typeToId[ns.types[j]] = ns.id;
47                 }
48                 nodeList.push(ns);
49             },
50             removeNodeSet: function(id) {
51                 var ns = nodeSets[id];
52                 for (var j=0;j<ns.types.length;j++) {
53                     if (ns.added) {
54                         // TODO: too tightly coupled into palette UI
55                         RED.palette.remove(ns.types[j]);
56                         var def = nodeDefinitions[ns.types[j]];
57                         if (def.onpaletteremove && typeof def.onpaletteremove === "function") {
58                             def.onpaletteremove.call(def);
59                         }
60                     }
61                     delete typeToId[ns.types[j]];
62                 }
63                 delete nodeSets[id];
64                 for (var i=0;i<nodeList.length;i++) {
65                     if (nodeList[i].id == id) {
66                         nodeList.splice(i,1);
67                         break;
68                     }
69                 }
70                 return ns;
71             },
72             getNodeSet: function(id) {
73                 return nodeSets[id];
74             },
75             enableNodeSet: function(id) {
76                 var ns = nodeSets[id];
77                 ns.enabled = true;
78                 for (var j=0;j<ns.types.length;j++) {
79                     // TODO: too tightly coupled into palette UI
80                     RED.palette.show(ns.types[j]);
81                     var def = nodeDefinitions[ns.types[j]];
82                     if (def.onpaletteadd && typeof def.onpaletteadd === "function") {
83                         def.onpaletteadd.call(def);
84                     }
85                 }
86             },
87             disableNodeSet: function(id) {
88                 var ns = nodeSets[id];
89                 ns.enabled = false;
90                 for (var j=0;j<ns.types.length;j++) {
91                     // TODO: too tightly coupled into palette UI
92                     RED.palette.hide(ns.types[j]);
93                     var def = nodeDefinitions[ns.types[j]];
94                     if (def.onpaletteremove && typeof def.onpaletteremove === "function") {
95                         def.onpaletteremove.call(def);
96                     }
97                 }
98             },
99             registerNodeType: function(nt,def) {
100                 nodeDefinitions[nt] = def;
101                 nodeSets[typeToId[nt]].added = true;
102                 // TODO: too tightly coupled into palette UI
103                 RED.palette.add(nt,def);
104                 if (def.onpaletteadd && typeof def.onpaletteadd === "function") {
105                     def.onpaletteadd.call(def);
106                 }
107             },
108             getNodeType: function(nt) {
109                 return nodeDefinitions[nt];
110             }
111         };
112         return exports;
113     })();
114     
115     function getID() {
116         return (1+Math.random()*4294967295).toString(16);
117     }
118
119     function addNode(n) {
120         if (n._def.category == "config") {
121             configNodes[n.id] = n;
122             RED.sidebar.config.refresh();
123         } else {
124             n.dirty = true;
125             nodes.push(n);
126             var updatedConfigNode = false;
127             for (var d in n._def.defaults) {
128                 if (n._def.defaults.hasOwnProperty(d)) {
129                     var property = n._def.defaults[d];
130                     if (property.type) {
131                         var type = registry.getNodeType(property.type);
132                         if (type && type.category == "config") {
133                             var configNode = configNodes[n[d]];
134                             if (configNode) {
135                                 updatedConfigNode = true;
136                                 configNode.users.push(n);
137                             }
138                         }
139                     }
140                 }
141             }
142             if (updatedConfigNode) {
143                 RED.sidebar.config.refresh();
144             }
145         }
146     }
147     function addLink(l) {
148         links.push(l);
149     }
150     function addConfig(c) {
151         configNodes[c.id] = c;
152     }
153
154     function getNode(id) {
155         if (id in configNodes) {
156             return configNodes[id];
157         } else {
158             for (var n in nodes) {
159                 if (nodes[n].id == id) {
160                     return nodes[n];
161                 }
162             }
163         }
164         return null;
165     }
166
167     function removeNode(id) {
168         var removedLinks = [];
169         if (id in configNodes) {
170             delete configNodes[id];
171             RED.sidebar.config.refresh();
172         } else {
173             var node = getNode(id);
174             if (node) {
175                 nodes.splice(nodes.indexOf(node),1);
176                 removedLinks = links.filter(function(l) { return (l.source === node) || (l.target === node); });
177                 removedLinks.map(function(l) {links.splice(links.indexOf(l), 1); });
178             }
179             var updatedConfigNode = false;
180             for (var d in node._def.defaults) {
181                 if (node._def.defaults.hasOwnProperty(d)) {
182                     var property = node._def.defaults[d];
183                     if (property.type) {
184                         var type = registry.getNodeType(property.type);
185                         if (type && type.category == "config") {
186                             var configNode = configNodes[node[d]];
187                             if (configNode) {
188                                 updatedConfigNode = true;
189                                 var users = configNode.users;
190                                 users.splice(users.indexOf(node),1);
191                             }
192                         }
193                     }
194                 }
195             }
196             if (updatedConfigNode) {
197                 RED.sidebar.config.refresh();
198             }
199         }
200         return removedLinks;
201     }
202
203     function removeLink(l) {
204         var index = links.indexOf(l);
205         if (index != -1) {
206             links.splice(index,1);
207         }
208     }
209
210     function refreshValidation() {
211         for (var n=0;n<nodes.length;n++) {
212             RED.editor.validateNode(nodes[n]);
213         }
214     }
215
216     function addWorkspace(ws) {
217         workspaces[ws.id] = ws;
218     }
219     function getWorkspace(id) {
220         return workspaces[id];
221     }
222     function removeWorkspace(id) {
223         delete workspaces[id];
224         var removedNodes = [];
225         var removedLinks = [];
226         var n;
227         for (n=0;n<nodes.length;n++) {
228             var node = nodes[n];
229             if (node.z == id) {
230                 removedNodes.push(node);
231             }
232         }
233         for (n=0;n<removedNodes.length;n++) {
234             var rmlinks = removeNode(removedNodes[n].id);
235             removedLinks = removedLinks.concat(rmlinks);
236         }
237         return {nodes:removedNodes,links:removedLinks};
238     }
239
240     function getAllFlowNodes(node) {
241         var visited = {};
242         visited[node.id] = true;
243         var nns = [node];
244         var stack = [node];
245         while(stack.length !== 0) {
246             var n = stack.shift();
247             var childLinks = links.filter(function(d) { return (d.source === n) || (d.target === n);});
248             for (var i=0;i<childLinks.length;i++) {
249                 var child = (childLinks[i].source === n)?childLinks[i].target:childLinks[i].source;
250                 if (!visited[child.id]) {
251                     visited[child.id] = true;
252                     nns.push(child);
253                     stack.push(child);
254                 }
255             }
256         }
257         return nns;
258     }
259
260     /**
261      * Converts a node to an exportable JSON Object
262      **/
263     function convertNode(n, exportCreds) {
264         exportCreds = exportCreds || false;
265         var node = {};
266         node.id = n.id;
267         node.type = n.type;
268         for (var d in n._def.defaults) {
269             if (n._def.defaults.hasOwnProperty(d)) {
270                 node[d] = n[d];
271             }
272         }
273         if(exportCreds && n.credentials) {
274             node.credentials = {};
275             for (var cred in n._def.credentials) {
276                 if (n._def.credentials.hasOwnProperty(cred)) {
277                     if (n.credentials[cred] != null) {
278                         node.credentials[cred] = n.credentials[cred];
279                     }
280                 }
281             }
282         }
283         if (n._def.category != "config") {
284             node.x = n.x;
285             node.y = n.y;
286             node.z = n.z;
287             node.wires = [];
288             for(var i=0;i<n.outputs;i++) {
289                 node.wires.push([]);
290             }
291             var wires = links.filter(function(d){return d.source === n;});
292             for (var j=0;j<wires.length;j++) {
293                 var w = wires[j];
294                 node.wires[w.sourcePort].push(w.target.id);
295             }
296         }
297         return node;
298     }
299
300     /**
301      * Converts the current node selection to an exportable JSON Object
302      **/
303     function createExportableNodeSet(set) {
304         var nns = [];
305         var exportedConfigNodes = {};
306         for (var n=0;n<set.length;n++) {
307             var node = set[n].n;
308             var convertedNode = RED.nodes.convertNode(node);
309             for (var d in node._def.defaults) {
310                 if (node._def.defaults[d].type && node[d] in configNodes) {
311                     var confNode = configNodes[node[d]];
312                     var exportable = registry.getNodeType(node._def.defaults[d].type).exportable;
313                     if ((exportable == null || exportable)) {
314                         if (!(node[d] in exportedConfigNodes)) {
315                             exportedConfigNodes[node[d]] = true;
316                             nns.unshift(RED.nodes.convertNode(confNode));
317                         }
318                     } else {
319                         convertedNode[d] = "";
320                     }
321                 }
322             }
323
324             nns.push(convertedNode);
325         }
326         return nns;
327     }
328
329     //TODO: rename this (createCompleteNodeSet)
330     function createCompleteNodeSet() {
331         var nns = [];
332         var i;
333         for (i in workspaces) {
334             if (workspaces.hasOwnProperty(i)) {
335                 nns.push(workspaces[i]);
336             }
337         }
338         for (i in configNodes) {
339             if (configNodes.hasOwnProperty(i)) {
340                 nns.push(convertNode(configNodes[i], true));
341             }
342         }
343         for (i=0;i<nodes.length;i++) {
344             var node = nodes[i];
345             nns.push(convertNode(node, true));
346         }
347         return nns;
348     }
349
350     function importNodes(newNodesObj,createNewIds) {
351         try {
352             var i;
353             var n;
354             var newNodes;
355             if (typeof newNodesObj === "string") {
356                 if (newNodesObj === "") {
357                     return;
358                 }
359                 newNodes = JSON.parse(newNodesObj);
360             } else {
361                 newNodes = newNodesObj;
362             }
363
364             if (!$.isArray(newNodes)) {
365                 newNodes = [newNodes];
366             }
367             var unknownTypes = [];
368             for (i=0;i<newNodes.length;i++) {
369                 n = newNodes[i];
370                 // TODO: remove workspace in next release+1
371                 if (n.type != "workspace" && n.type != "tab" && !registry.getNodeType(n.type)) {
372                     // TODO: get this UI thing out of here! (see below as well)
373                     n.name = n.type;
374                     n.type = "unknown";
375                     if (unknownTypes.indexOf(n.name)==-1) {
376                         unknownTypes.push(n.name);
377                     }
378                     if (n.x == null && n.y == null) {
379                         // config node - remove it
380                         newNodes.splice(i,1);
381                         i--;
382                     }
383                 }
384             }
385             if (unknownTypes.length > 0) {
386                 var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
387                 var type = "type"+(unknownTypes.length > 1?"s":"");
388                 RED.notify("<strong>Imported unrecognised "+type+":</strong>"+typeList,"error",false,10000);
389                 //"DO NOT DEPLOY while in this state.<br/>Either, add missing types to Node-RED, restart and then reload page,<br/>or delete unknown "+n.name+", rewire as required, and then deploy.","error");
390             }
391
392             var new_workspaces = [];
393             var workspace_map = {};
394             
395             for (i=0;i<newNodes.length;i++) {
396                 n = newNodes[i];
397                 // TODO: remove workspace in next release+1
398                 if (n.type === "workspace" || n.type === "tab") {
399                     if (n.type === "workspace") {
400                         n.type = "tab";
401                     }
402                     if (defaultWorkspace == null) {
403                         defaultWorkspace = n;
404                     }
405                     if (createNewIds) {
406                         var nid = getID();
407                         workspace_map[n.id] = nid;
408                         n.id = nid;
409                     }
410                     addWorkspace(n);
411                     RED.view.addWorkspace(n);
412                     new_workspaces.push(n);
413                 }
414             }
415             if (defaultWorkspace == null) {
416                 defaultWorkspace = { type:"tab", id:getID(), label:"Sheet 1" };
417                 addWorkspace(defaultWorkspace);
418                 RED.view.addWorkspace(defaultWorkspace);
419                 new_workspaces.push(defaultWorkspace);
420             }
421
422             var node_map = {};
423             var new_nodes = [];
424             var new_links = [];
425
426             for (i=0;i<newNodes.length;i++) {
427                 n = newNodes[i];
428                 // TODO: remove workspace in next release+1
429                 if (n.type !== "workspace" && n.type !== "tab") {
430                     var def = registry.getNodeType(n.type);
431                     if (def && def.category == "config") {
432                         if (!RED.nodes.node(n.id)) {
433                             var configNode = {id:n.id,type:n.type,users:[]};
434                             for (var d in def.defaults) {
435                                 if (def.defaults.hasOwnProperty(d)) {
436                                     configNode[d] = n[d];
437                                 }
438                             }
439                             configNode.label = def.label;
440                             configNode._def = def;
441                             RED.nodes.add(configNode);
442                         }
443                     } else {
444                         var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false};
445                         if (createNewIds) {
446                             node.z = workspace_map[node.z];
447                             if (!workspaces[node.z]) {
448                                 node.z = RED.view.getWorkspace();
449                             }
450                             node.id = getID();
451                         } else {
452                             node.id = n.id;
453                             if (node.z == null || !workspaces[node.z]) {
454                                 node.z = RED.view.getWorkspace();
455                             }
456                         }
457                         node.type = n.type;
458                         node._def = def;
459                         if (!node._def) {
460                             node._def = {
461                                 color:"#fee",
462                                 defaults: {},
463                                 label: "unknown: "+n.type,
464                                 labelStyle: "node_label_italic",
465                                 outputs: n.outputs||n.wires.length
466                             }
467                         }
468                         node.outputs = n.outputs||node._def.outputs;
469
470                         for (var d2 in node._def.defaults) {
471                             if (node._def.defaults.hasOwnProperty(d2)) {
472                                 node[d2] = n[d2];
473                             }
474                         }
475
476                         addNode(node);
477                         RED.editor.validateNode(node);
478                         node_map[n.id] = node;
479                         new_nodes.push(node);
480                     }
481                 }
482             }
483             for (i=0;i<new_nodes.length;i++) {
484                 n = new_nodes[i];
485                 for (var w1=0;w1<n.wires.length;w1++) {
486                     var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
487                     for (var w2=0;w2<wires.length;w2++) {
488                         if (wires[w2] in node_map) {
489                             var link = {source:n,sourcePort:w1,target:node_map[wires[w2]]};
490                             addLink(link);
491                             new_links.push(link);
492                         }
493                     }
494                 }
495                 delete n.wires;
496             }
497             return [new_nodes,new_links,new_workspaces];
498         } catch(error) {
499             //TODO: get this UI thing out of here! (see above as well)
500             RED.notify("<strong>Error</strong>: "+error,"error");
501             return null;
502         }
503
504     }
505
506     return {
507         registry:registry,
508         setNodeList: registry.setNodeList,
509         
510         getNodeSet: registry.getNodeSet,
511         addNodeSet: registry.addNodeSet,
512         removeNodeSet: registry.removeNodeSet,
513         enableNodeSet: registry.enableNodeSet,
514         disableNodeSet: registry.disableNodeSet,
515         
516         registerType: registry.registerNodeType,
517         getType: registry.getNodeType,
518         convertNode: convertNode,
519         add: addNode,
520         addLink: addLink,
521         remove: removeNode,
522         removeLink: removeLink,
523         addWorkspace: addWorkspace,
524         removeWorkspace: removeWorkspace,
525         workspace: getWorkspace,
526         eachNode: function(cb) {
527             for (var n=0;n<nodes.length;n++) {
528                 cb(nodes[n]);
529             }
530         },
531         eachLink: function(cb) {
532             for (var l=0;l<links.length;l++) {
533                 cb(links[l]);
534             }
535         },
536         eachConfig: function(cb) {
537             for (var id in configNodes) {
538                 if (configNodes.hasOwnProperty(id)) {
539                     cb(configNodes[id]);
540                 }
541             }
542         },
543         node: getNode,
544         import: importNodes,
545         refreshValidation: refreshValidation,
546         getAllFlowNodes: getAllFlowNodes,
547         createExportableNodeSet: createExportableNodeSet,
548         createCompleteNodeSet: createCompleteNodeSet,
549         id: getID,
550         nodes: nodes, // TODO: exposed for d3 vis
551         links: links  // TODO: exposed for d3 vis
552     };
553 })();