2 * Copyright 2013 IBM Corp.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 RED.nodes = (function() {
25 var registry = (function() {
29 var nodeDefinitions = {};
32 getNodeList: function() {
35 setNodeList: function(list) {
37 for(var i=0;i<list.length;i++) {
39 exports.addNodeSet(ns);
42 addNodeSet: function(ns) {
45 for (var j=0;j<ns.types.length;j++) {
46 typeToId[ns.types[j]] = ns.id;
50 removeNodeSet: function(id) {
51 var ns = nodeSets[id];
52 for (var j=0;j<ns.types.length;j++) {
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);
61 delete typeToId[ns.types[j]];
64 for (var i=0;i<nodeList.length;i++) {
65 if (nodeList[i].id == id) {
72 getNodeSet: function(id) {
75 enableNodeSet: function(id) {
76 var ns = nodeSets[id];
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);
87 disableNodeSet: function(id) {
88 var ns = nodeSets[id];
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);
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);
108 getNodeType: function(nt) {
109 return nodeDefinitions[nt];
116 return (1+Math.random()*4294967295).toString(16);
119 function addNode(n) {
120 if (n._def.category == "config") {
121 configNodes[n.id] = n;
122 RED.sidebar.config.refresh();
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];
131 var type = registry.getNodeType(property.type);
132 if (type && type.category == "config") {
133 var configNode = configNodes[n[d]];
135 updatedConfigNode = true;
136 configNode.users.push(n);
142 if (updatedConfigNode) {
143 RED.sidebar.config.refresh();
147 function addLink(l) {
150 function addConfig(c) {
151 configNodes[c.id] = c;
154 function getNode(id) {
155 if (id in configNodes) {
156 return configNodes[id];
158 for (var n in nodes) {
159 if (nodes[n].id == id) {
167 function removeNode(id) {
168 var removedLinks = [];
169 if (id in configNodes) {
170 delete configNodes[id];
171 RED.sidebar.config.refresh();
173 var node = getNode(id);
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); });
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];
184 var type = registry.getNodeType(property.type);
185 if (type && type.category == "config") {
186 var configNode = configNodes[node[d]];
188 updatedConfigNode = true;
189 var users = configNode.users;
190 users.splice(users.indexOf(node),1);
196 if (updatedConfigNode) {
197 RED.sidebar.config.refresh();
203 function removeLink(l) {
204 var index = links.indexOf(l);
206 links.splice(index,1);
210 function refreshValidation() {
211 for (var n=0;n<nodes.length;n++) {
212 RED.editor.validateNode(nodes[n]);
216 function addWorkspace(ws) {
217 workspaces[ws.id] = ws;
219 function getWorkspace(id) {
220 return workspaces[id];
222 function removeWorkspace(id) {
223 delete workspaces[id];
224 var removedNodes = [];
225 var removedLinks = [];
227 for (n=0;n<nodes.length;n++) {
230 removedNodes.push(node);
233 for (n=0;n<removedNodes.length;n++) {
234 var rmlinks = removeNode(removedNodes[n].id);
235 removedLinks = removedLinks.concat(rmlinks);
237 return {nodes:removedNodes,links:removedLinks};
240 function getAllFlowNodes(node) {
242 visited[node.id] = true;
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;
261 * Converts a node to an exportable JSON Object
263 function convertNode(n, exportCreds) {
264 exportCreds = exportCreds || false;
268 for (var d in n._def.defaults) {
269 if (n._def.defaults.hasOwnProperty(d)) {
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];
283 if (n._def.category != "config") {
288 for(var i=0;i<n.outputs;i++) {
291 var wires = links.filter(function(d){return d.source === n;});
292 for (var j=0;j<wires.length;j++) {
294 node.wires[w.sourcePort].push(w.target.id);
301 * Converts the current node selection to an exportable JSON Object
303 function createExportableNodeSet(set) {
305 var exportedConfigNodes = {};
306 for (var n=0;n<set.length;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));
319 convertedNode[d] = "";
324 nns.push(convertedNode);
329 //TODO: rename this (createCompleteNodeSet)
330 function createCompleteNodeSet() {
333 for (i in workspaces) {
334 if (workspaces.hasOwnProperty(i)) {
335 nns.push(workspaces[i]);
338 for (i in configNodes) {
339 if (configNodes.hasOwnProperty(i)) {
340 nns.push(convertNode(configNodes[i], true));
343 for (i=0;i<nodes.length;i++) {
345 nns.push(convertNode(node, true));
350 function importNodes(newNodesObj,createNewIds) {
355 if (typeof newNodesObj === "string") {
356 if (newNodesObj === "") {
359 newNodes = JSON.parse(newNodesObj);
361 newNodes = newNodesObj;
364 if (!$.isArray(newNodes)) {
365 newNodes = [newNodes];
367 var unknownTypes = [];
368 for (i=0;i<newNodes.length;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)
375 if (unknownTypes.indexOf(n.name)==-1) {
376 unknownTypes.push(n.name);
378 if (n.x == null && n.y == null) {
379 // config node - remove it
380 newNodes.splice(i,1);
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");
392 var new_workspaces = [];
393 var workspace_map = {};
395 for (i=0;i<newNodes.length;i++) {
397 // TODO: remove workspace in next release+1
398 if (n.type === "workspace" || n.type === "tab") {
399 if (n.type === "workspace") {
402 if (defaultWorkspace == null) {
403 defaultWorkspace = n;
407 workspace_map[n.id] = nid;
411 RED.view.addWorkspace(n);
412 new_workspaces.push(n);
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);
426 for (i=0;i<newNodes.length;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];
439 configNode.label = def.label;
440 configNode._def = def;
441 RED.nodes.add(configNode);
444 var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false};
446 node.z = workspace_map[node.z];
447 if (!workspaces[node.z]) {
448 node.z = RED.view.getWorkspace();
453 if (node.z == null || !workspaces[node.z]) {
454 node.z = RED.view.getWorkspace();
463 label: "unknown: "+n.type,
464 labelStyle: "node_label_italic",
465 outputs: n.outputs||n.wires.length
468 node.outputs = n.outputs||node._def.outputs;
470 for (var d2 in node._def.defaults) {
471 if (node._def.defaults.hasOwnProperty(d2)) {
477 RED.editor.validateNode(node);
478 node_map[n.id] = node;
479 new_nodes.push(node);
483 for (i=0;i<new_nodes.length;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]]};
491 new_links.push(link);
497 return [new_nodes,new_links,new_workspaces];
499 //TODO: get this UI thing out of here! (see above as well)
500 RED.notify("<strong>Error</strong>: "+error,"error");
508 setNodeList: registry.setNodeList,
510 getNodeSet: registry.getNodeSet,
511 addNodeSet: registry.addNodeSet,
512 removeNodeSet: registry.removeNodeSet,
513 enableNodeSet: registry.enableNodeSet,
514 disableNodeSet: registry.disableNodeSet,
516 registerType: registry.registerNodeType,
517 getType: registry.getNodeType,
518 convertNode: convertNode,
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++) {
531 eachLink: function(cb) {
532 for (var l=0;l<links.length;l++) {
536 eachConfig: function(cb) {
537 for (var id in configNodes) {
538 if (configNodes.hasOwnProperty(id)) {
545 refreshValidation: refreshValidation,
546 getAllFlowNodes: getAllFlowNodes,
547 createExportableNodeSet: createExportableNodeSet,
548 createCompleteNodeSet: createCompleteNodeSet,
550 nodes: nodes, // TODO: exposed for d3 vis
551 links: links // TODO: exposed for d3 vis