mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge branch 'pr_2498' into dev
This commit is contained in:
commit
5d81cec00c
@ -87,6 +87,9 @@
|
|||||||
expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
|
expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
|
||||||
}
|
}
|
||||||
dialogForm.i18n();
|
dialogForm.i18n();
|
||||||
|
setTimeout(function() {
|
||||||
|
expressionEditor.focus();
|
||||||
|
},300);
|
||||||
},
|
},
|
||||||
close: function() {
|
close: function() {
|
||||||
expressionEditor.destroy();
|
expressionEditor.destroy();
|
||||||
|
@ -68,6 +68,8 @@ RED.library = (function() {
|
|||||||
var field = activeLibrary.fields[i];
|
var field = activeLibrary.fields[i];
|
||||||
if (field == "name") {
|
if (field == "name") {
|
||||||
data.name = name;
|
data.name = name;
|
||||||
|
} else if (typeof(field) === 'object') {
|
||||||
|
data[field.name] = field.get();
|
||||||
} else {
|
} else {
|
||||||
data[field] = $("#"+elementPrefix+field).val();
|
data[field] = $("#"+elementPrefix+field).val();
|
||||||
}
|
}
|
||||||
@ -539,7 +541,13 @@ RED.library = (function() {
|
|||||||
var elementPrefix = activeLibrary.elementPrefix || "node-input-";
|
var elementPrefix = activeLibrary.elementPrefix || "node-input-";
|
||||||
for (var i=0; i<activeLibrary.fields.length; i++) {
|
for (var i=0; i<activeLibrary.fields.length; i++) {
|
||||||
var field = activeLibrary.fields[i];
|
var field = activeLibrary.fields[i];
|
||||||
$("#"+elementPrefix+field).val(selectedLibraryItem[field]);
|
if (typeof(field) === 'object') {
|
||||||
|
var val = selectedLibraryItem[field.name];
|
||||||
|
field.set(val);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#"+elementPrefix+field).val(selectedLibraryItem[field]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
activeLibrary.editor.setValue(libraryEditor.getValue(),-1);
|
activeLibrary.editor.setValue(libraryEditor.getValue(),-1);
|
||||||
}
|
}
|
||||||
|
@ -4,19 +4,54 @@
|
|||||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||||
<div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div>
|
<div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row" style="margin-bottom: 0px;">
|
|
||||||
<label for="node-input-func"><i class="fa fa-wrench"></i> <span data-i18n="function.label.function"></span></label>
|
<div class="form-row">
|
||||||
<input type="hidden" id="node-input-func" autofocus="autofocus">
|
<ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul>
|
||||||
<input type="hidden" id="node-input-noerr">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row node-text-editor-row" style="position:relative">
|
|
||||||
<div style="position: absolute; right:0; bottom:calc(100% + 3px);"><button id="node-function-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
<div id="func-tabs-content" style="min-height: calc(100% - 95px);">
|
||||||
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-func-editor" ></div>
|
|
||||||
</div>
|
<div id="func-tab-init" style="display:none">
|
||||||
<div class="form-row" style="margin-bottom: 0px">
|
<div class="form-row" style="margin-bottom: 0px;">
|
||||||
<label for="node-input-outputs"><i class="fa fa-random"></i> <span data-i18n="function.label.outputs"></span></label>
|
<input type="hidden" id="node-input-initialize" autofocus="autofocus">
|
||||||
<input id="node-input-outputs" style="width: 60px;" value="1">
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row node-text-editor-row" style="position:relative">
|
||||||
|
<div style="position: absolute; right:0; bottom: calc(100% + 3px);"><button id="node-init-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
||||||
|
<div style="height: 250px; min-height:150px; margin-top: 30px;" class="node-text-editor" id="node-input-init-editor" ></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="func-tab-body" style="display:none">
|
||||||
|
<div class="form-row" style="margin-bottom: 0px;">
|
||||||
|
<input type="hidden" id="node-input-func" autofocus="autofocus">
|
||||||
|
<input type="hidden" id="node-input-noerr">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row node-text-editor-row" style="position:relative">
|
||||||
|
<div style="position: absolute; right:0; bottom: calc(100% + 3px);"><button id="node-function-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
||||||
|
<div style="height: 220px; min-height:120px; margin-top: 30px;" class="node-text-editor" id="node-input-func-editor" ></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row" style="margin-bottom: 0px">
|
||||||
|
<label for="node-input-outputs"><i class="fa fa-random"></i> <span data-i18n="function.label.outputs"></span></label>
|
||||||
|
<input id="node-input-outputs" style="width: 60px;" value="1">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="func-tab-finalize" style="display:none">
|
||||||
|
<div class="form-row" style="margin-bottom: 0px;">
|
||||||
|
<input type="hidden" id="node-input-finalize" autofocus="autofocus">
|
||||||
|
</div>
|
||||||
|
<div class="form-row node-text-editor-row" style="position:relative">
|
||||||
|
<div style="position: absolute; right:0; bottom: calc(100% + 3px);"><button id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
|
||||||
|
<div style="height: 250px; min-height:150px; margin-top: 30px;" class="node-text-editor" id="node-input-finalize-editor" ></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@ -27,7 +62,9 @@
|
|||||||
name: {value:""},
|
name: {value:""},
|
||||||
func: {value:"\nreturn msg;"},
|
func: {value:"\nreturn msg;"},
|
||||||
outputs: {value:1},
|
outputs: {value:1},
|
||||||
noerr: {value:0,required:true,validate:function(v) { return !v; }}
|
noerr: {value:0,required:true,validate:function(v) { return !v; }},
|
||||||
|
initialize: {value:""},
|
||||||
|
finalize: {value:""}
|
||||||
},
|
},
|
||||||
inputs:1,
|
inputs:1,
|
||||||
outputs:1,
|
outputs:1,
|
||||||
@ -40,6 +77,28 @@
|
|||||||
},
|
},
|
||||||
oneditprepare: function() {
|
oneditprepare: function() {
|
||||||
var that = this;
|
var that = this;
|
||||||
|
|
||||||
|
var tabs = RED.tabs.create({
|
||||||
|
id: "func-tabs",
|
||||||
|
onchange: function(tab) {
|
||||||
|
$("#func-tabs-content").children().hide();
|
||||||
|
$("#" + tab.id).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tabs.addTab({
|
||||||
|
id: "func-tab-init",
|
||||||
|
label: that._("function.label.initialize")
|
||||||
|
});
|
||||||
|
tabs.addTab({
|
||||||
|
id: "func-tab-body",
|
||||||
|
label: that._("function.label.function")
|
||||||
|
});
|
||||||
|
tabs.addTab({
|
||||||
|
id: "func-tab-finalize",
|
||||||
|
label: that._("function.label.finalize")
|
||||||
|
});
|
||||||
|
tabs.activateTab("func-tab-body");
|
||||||
|
|
||||||
$( "#node-input-outputs" ).spinner({
|
$( "#node-input-outputs" ).spinner({
|
||||||
min:0,
|
min:0,
|
||||||
change: function(event, ui) {
|
change: function(event, ui) {
|
||||||
@ -50,74 +109,136 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.editor = RED.editor.createEditor({
|
var buildEditor = function(id, value, defaultValue) {
|
||||||
id: 'node-input-func-editor',
|
var editor = RED.editor.createEditor({
|
||||||
mode: 'ace/mode/nrjavascript',
|
id: id,
|
||||||
value: $("#node-input-func").val(),
|
mode: 'ace/mode/nrjavascript',
|
||||||
globals: {
|
value: value || defaultValue || "",
|
||||||
msg:true,
|
globals: {
|
||||||
context:true,
|
msg:true,
|
||||||
RED: true,
|
context:true,
|
||||||
util: true,
|
RED: true,
|
||||||
flow: true,
|
util: true,
|
||||||
global: true,
|
flow: true,
|
||||||
console: true,
|
global: true,
|
||||||
Buffer: true,
|
console: true,
|
||||||
setTimeout: true,
|
Buffer: true,
|
||||||
clearTimeout: true,
|
setTimeout: true,
|
||||||
setInterval: true,
|
clearTimeout: true,
|
||||||
clearInterval: true
|
setInterval: true,
|
||||||
|
clearInterval: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (defaultValue && value === "") {
|
||||||
|
editor.moveCursorTo(defaultValue.split("\n").length - 1, 0);
|
||||||
}
|
}
|
||||||
});
|
return editor;
|
||||||
|
}
|
||||||
|
this.initEditor = buildEditor('node-input-init-editor',$("#node-input-initialize").val(),RED._("node-red:function.text.initialize"))
|
||||||
|
this.editor = buildEditor('node-input-func-editor',$("#node-input-func").val())
|
||||||
|
this.finalizeEditor = buildEditor('node-input-finalize-editor',$("#node-input-finalize").val(),RED._("node-red:function.text.finalize"))
|
||||||
|
|
||||||
RED.library.create({
|
RED.library.create({
|
||||||
url:"functions", // where to get the data from
|
url:"functions", // where to get the data from
|
||||||
type:"function", // the type of object the library is for
|
type:"function", // the type of object the library is for
|
||||||
editor:this.editor, // the field name the main text body goes to
|
editor:this.editor, // the field name the main text body goes to
|
||||||
mode:"ace/mode/nrjavascript",
|
mode:"ace/mode/nrjavascript",
|
||||||
fields:['name','outputs'],
|
fields:[
|
||||||
|
'name', 'outputs',
|
||||||
|
{
|
||||||
|
name: 'initialize',
|
||||||
|
get: function() {
|
||||||
|
return that.initEditor.getValue();
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
|
that.initEditor.setValue(v||RED._("node-red:function.text.initialize"), -1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'finalize',
|
||||||
|
get: function() {
|
||||||
|
return that.finalizeEditor.getValue();
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
|
that.finalizeEditor.setValue(v||RED._("node-red:function.text.finalize"), -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
ext:"js"
|
ext:"js"
|
||||||
});
|
});
|
||||||
this.editor.focus();
|
this.editor.focus();
|
||||||
|
|
||||||
RED.popover.tooltip($("#node-function-expand-js"), RED._("node-red:common.label.expand"));
|
|
||||||
|
|
||||||
$("#node-function-expand-js").on("click", function(e) {
|
var expandButtonClickHandler = function(editor) {
|
||||||
e.preventDefault();
|
return function(e) {
|
||||||
var value = that.editor.getValue();
|
e.preventDefault();
|
||||||
RED.editor.editJavaScript({
|
var value = editor.getValue();
|
||||||
value: value,
|
RED.editor.editJavaScript({
|
||||||
width: "Infinity",
|
value: value,
|
||||||
cursor: that.editor.getCursorPosition(),
|
width: "Infinity",
|
||||||
mode: "ace/mode/nrjavascript",
|
cursor: editor.getCursorPosition(),
|
||||||
complete: function(v,cursor) {
|
mode: "ace/mode/nrjavascript",
|
||||||
that.editor.setValue(v, -1);
|
complete: function(v,cursor) {
|
||||||
that.editor.gotoLine(cursor.row+1,cursor.column,false);
|
editor.setValue(v, -1);
|
||||||
setTimeout(function() {
|
editor.gotoLine(cursor.row+1,cursor.column,false);
|
||||||
that.editor.focus();
|
setTimeout(function() {
|
||||||
},300);
|
editor.focus();
|
||||||
}
|
},300);
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
},
|
|
||||||
oneditsave: function() {
|
|
||||||
var annot = this.editor.getSession().getAnnotations();
|
|
||||||
this.noerr = 0;
|
|
||||||
$("#node-input-noerr").val(0);
|
|
||||||
for (var k=0; k < annot.length; k++) {
|
|
||||||
//console.log(annot[k].type,":",annot[k].text, "on line", annot[k].row);
|
|
||||||
if (annot[k].type === "error") {
|
|
||||||
$("#node-input-noerr").val(annot.length);
|
|
||||||
this.noerr = annot.length;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$("#node-input-func").val(this.editor.getValue());
|
$("#node-init-expand-js").on("click", expandButtonClickHandler(this.initEditor));
|
||||||
this.editor.destroy();
|
$("#node-function-expand-js").on("click", expandButtonClickHandler(this.editor));
|
||||||
delete this.editor;
|
$("#node-finalize-expand-js").on("click", expandButtonClickHandler(this.finalizeEditor));
|
||||||
|
|
||||||
|
RED.popover.tooltip($("#node-init-expand-js"), RED._("node-red:common.label.expand"));
|
||||||
|
RED.popover.tooltip($("#node-function-expand-js"), RED._("node-red:common.label.expand"));
|
||||||
|
RED.popover.tooltip($("#node-finalize-expand-js"), RED._("node-red:common.label.expand"));
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
oneditsave: function() {
|
||||||
|
var node = this;
|
||||||
|
var noerr = 0;
|
||||||
|
$("#node-input-noerr").val(0);
|
||||||
|
|
||||||
|
var disposeEditor = function(editorName,targetName,defaultValue) {
|
||||||
|
var editor = node[editorName];
|
||||||
|
var annot = editor.getSession().getAnnotations();
|
||||||
|
for (var k=0; k < annot.length; k++) {
|
||||||
|
if (annot[k].type === "error") {
|
||||||
|
noerr += annot.length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var val = editor.getValue();
|
||||||
|
if (defaultValue && val == defaultValue) {
|
||||||
|
val = "";
|
||||||
|
}
|
||||||
|
editor.destroy();
|
||||||
|
delete node[editorName];
|
||||||
|
$("#"+targetName).val(val);
|
||||||
|
}
|
||||||
|
disposeEditor("editor","node-input-func");
|
||||||
|
disposeEditor("initEditor","node-input-initialize", RED._("node-red:function.text.initialize"));
|
||||||
|
disposeEditor("finalizeEditor","node-input-finalize", RED._("node-red:function.text.finalize"));
|
||||||
|
|
||||||
|
$("#node-input-noerr").val(noerr);
|
||||||
|
this.noerr = noerr;
|
||||||
|
|
||||||
},
|
},
|
||||||
oneditcancel: function() {
|
oneditcancel: function() {
|
||||||
this.editor.destroy();
|
var node = this;
|
||||||
delete this.editor;
|
|
||||||
|
node.editor.destroy();
|
||||||
|
delete node.editor;
|
||||||
|
|
||||||
|
node.initEditor.destroy();
|
||||||
|
delete node.initEditor;
|
||||||
|
|
||||||
|
node.finalizeEditor.destroy();
|
||||||
|
delete node.finalizeEditor;
|
||||||
},
|
},
|
||||||
oneditresize: function(size) {
|
oneditresize: function(size) {
|
||||||
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||||
@ -129,6 +250,16 @@
|
|||||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||||
$(".node-text-editor").css("height",height+"px");
|
$(".node-text-editor").css("height",height+"px");
|
||||||
this.editor.resize();
|
this.editor.resize();
|
||||||
|
|
||||||
|
var height = size.height;
|
||||||
|
$("#node-input-init-editor").css("height", (height -105)+"px");
|
||||||
|
$("#node-input-func-editor").css("height", (height -145)+"px");
|
||||||
|
$("#node-input-finalize-editor").css("height", (height -105)+"px");
|
||||||
|
|
||||||
|
this.initEditor.resize();
|
||||||
|
this.editor.resize();
|
||||||
|
this.finalizeEditor.resize();
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -57,17 +57,50 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createVMOpt(node, kind) {
|
||||||
|
var opt = {
|
||||||
|
filename: 'Function node'+kind+':'+node.id+(node.name?' ['+node.name+']':''), // filename for stack traces
|
||||||
|
displayErrors: true
|
||||||
|
// Using the following options causes node 4/6 to not include the line number
|
||||||
|
// in the stack output. So don't use them.
|
||||||
|
// lineOffset: -11, // line number offset to be used for stack traces
|
||||||
|
// columnOffset: 0, // column number offset to be used for stack traces
|
||||||
|
};
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateErrorInfo(err) {
|
||||||
|
if (err.stack) {
|
||||||
|
var stack = err.stack.toString();
|
||||||
|
var m = /^([^:]+):([^:]+):(\d+).*/.exec(stack);
|
||||||
|
if (m) {
|
||||||
|
var line = parseInt(m[3]) -1;
|
||||||
|
var kind = "body:";
|
||||||
|
if (/setup/.exec(m[1])) {
|
||||||
|
kind = "setup:";
|
||||||
|
}
|
||||||
|
if (/cleanup/.exec(m[1])) {
|
||||||
|
kind = "cleanup:";
|
||||||
|
}
|
||||||
|
err.message += " ("+kind+"line "+line+")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function FunctionNode(n) {
|
function FunctionNode(n) {
|
||||||
RED.nodes.createNode(this,n);
|
RED.nodes.createNode(this,n);
|
||||||
var node = this;
|
var node = this;
|
||||||
this.name = n.name;
|
node.name = n.name;
|
||||||
this.func = n.func;
|
node.func = n.func;
|
||||||
|
node.ini = n.initialize ? n.initialize : "";
|
||||||
|
node.fin = n.finalize ? n.finalize : "";
|
||||||
|
|
||||||
var handleNodeDoneCall = true;
|
var handleNodeDoneCall = true;
|
||||||
|
|
||||||
// Check to see if the Function appears to call `node.done()`. If so,
|
// Check to see if the Function appears to call `node.done()`. If so,
|
||||||
// we will assume it is well written and does actually call node.done().
|
// we will assume it is well written and does actually call node.done().
|
||||||
// Otherwise, we will call node.done() after the function returns regardless.
|
// Otherwise, we will call node.done() after the function returns regardless.
|
||||||
if (/node\.done\s*\(\s*\)/.test(this.func)) {
|
if (/node\.done\s*\(\s*\)/.test(node.func)) {
|
||||||
handleNodeDoneCall = false;
|
handleNodeDoneCall = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,11 +120,13 @@ module.exports = function(RED) {
|
|||||||
"send:function(msgs,cloneMsg){ __node__.send(__send__,__msgid__,msgs,cloneMsg);},"+
|
"send:function(msgs,cloneMsg){ __node__.send(__send__,__msgid__,msgs,cloneMsg);},"+
|
||||||
"done:__done__"+
|
"done:__done__"+
|
||||||
"};\n"+
|
"};\n"+
|
||||||
this.func+"\n"+
|
node.func+"\n"+
|
||||||
"})(msg,send,done);";
|
"})(msg,send,done);";
|
||||||
this.topic = n.topic;
|
var finScript = null;
|
||||||
this.outstandingTimers = [];
|
var finOpt = null;
|
||||||
this.outstandingIntervals = [];
|
node.topic = n.topic;
|
||||||
|
node.outstandingTimers = [];
|
||||||
|
node.outstandingIntervals = [];
|
||||||
var sandbox = {
|
var sandbox = {
|
||||||
console:console,
|
console:console,
|
||||||
util:util,
|
util:util,
|
||||||
@ -182,12 +217,12 @@ module.exports = function(RED) {
|
|||||||
arguments[0] = function() {
|
arguments[0] = function() {
|
||||||
sandbox.clearTimeout(timerId);
|
sandbox.clearTimeout(timerId);
|
||||||
try {
|
try {
|
||||||
func.apply(this,arguments);
|
func.apply(node,arguments);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
node.error(err,{});
|
node.error(err,{});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
timerId = setTimeout.apply(this,arguments);
|
timerId = setTimeout.apply(node,arguments);
|
||||||
node.outstandingTimers.push(timerId);
|
node.outstandingTimers.push(timerId);
|
||||||
return timerId;
|
return timerId;
|
||||||
},
|
},
|
||||||
@ -203,12 +238,12 @@ module.exports = function(RED) {
|
|||||||
var timerId;
|
var timerId;
|
||||||
arguments[0] = function() {
|
arguments[0] = function() {
|
||||||
try {
|
try {
|
||||||
func.apply(this,arguments);
|
func.apply(node,arguments);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
node.error(err,{});
|
node.error(err,{});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
timerId = setInterval.apply(this,arguments);
|
timerId = setInterval.apply(node,arguments);
|
||||||
node.outstandingIntervals.push(timerId);
|
node.outstandingIntervals.push(timerId);
|
||||||
return timerId;
|
return timerId;
|
||||||
},
|
},
|
||||||
@ -226,35 +261,46 @@ module.exports = function(RED) {
|
|||||||
sandbox.setTimeout(function(){ resolve(value); }, after);
|
sandbox.setTimeout(function(){ resolve(value); }, after);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
sandbox.promisify = util.promisify;
|
||||||
}
|
}
|
||||||
var context = vm.createContext(sandbox);
|
var context = vm.createContext(sandbox);
|
||||||
try {
|
try {
|
||||||
this.script = vm.createScript(functionText, {
|
var iniScript = null;
|
||||||
filename: 'Function node:'+this.id+(this.name?' ['+this.name+']':''), // filename for stack traces
|
var iniOpt = null;
|
||||||
displayErrors: true
|
if (node.ini && (node.ini !== "")) {
|
||||||
// Using the following options causes node 4/6 to not include the line number
|
var iniText = "(async function () {\n"+node.ini +"\n})();";
|
||||||
// in the stack output. So don't use them.
|
iniOpt = createVMOpt(node, " setup");
|
||||||
// lineOffset: -11, // line number offset to be used for stack traces
|
iniScript = new vm.Script(iniText, iniOpt);
|
||||||
// columnOffset: 0, // column number offset to be used for stack traces
|
}
|
||||||
});
|
node.script = vm.createScript(functionText, createVMOpt(node, ""));
|
||||||
this.on("input", function(msg,send,done) {
|
if (node.fin && (node.fin !== "")) {
|
||||||
|
var finText = "(function () {\n"+node.fin +"\n})();";
|
||||||
|
finOpt = createVMOpt(node, " cleanup");
|
||||||
|
finScript = new vm.Script(finText, finOpt);
|
||||||
|
}
|
||||||
|
var promise = Promise.resolve();
|
||||||
|
if (iniScript) {
|
||||||
|
promise = iniScript.runInContext(context, iniOpt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function processMessage(msg, send, done) {
|
||||||
try {
|
try {
|
||||||
var start = process.hrtime();
|
var start = process.hrtime();
|
||||||
context.msg = msg;
|
context.msg = msg;
|
||||||
context.send = send;
|
context.send = send;
|
||||||
context.done = done;
|
context.done = done;
|
||||||
|
|
||||||
this.script.runInContext(context);
|
node.script.runInContext(context);
|
||||||
sendResults(this,send,msg._msgid,context.results,false);
|
sendResults(node,send,msg._msgid,context.results,false);
|
||||||
if (handleNodeDoneCall) {
|
if (handleNodeDoneCall) {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
|
|
||||||
var duration = process.hrtime(start);
|
var duration = process.hrtime(start);
|
||||||
var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100;
|
var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100;
|
||||||
this.metric("duration", msg, converted);
|
node.metric("duration", msg, converted);
|
||||||
if (process.env.NODE_RED_FUNCTION_TIME) {
|
if (process.env.NODE_RED_FUNCTION_TIME) {
|
||||||
this.status({fill:"yellow",shape:"dot",text:""+converted});
|
node.status({fill:"yellow",shape:"dot",text:""+converted});
|
||||||
}
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
if ((typeof err === "object") && err.hasOwnProperty("stack")) {
|
if ((typeof err === "object") && err.hasOwnProperty("stack")) {
|
||||||
@ -295,22 +341,66 @@ module.exports = function(RED) {
|
|||||||
done(JSON.stringify(err));
|
done(JSON.stringify(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RESOLVING = 0;
|
||||||
|
const RESOLVED = 1;
|
||||||
|
const ERROR = 2;
|
||||||
|
var state = RESOLVING;
|
||||||
|
var messages = [];
|
||||||
|
|
||||||
|
node.on("input", function(msg,send,done) {
|
||||||
|
if(state === RESOLVING) {
|
||||||
|
messages.push({msg:msg, send:send, done:done});
|
||||||
|
}
|
||||||
|
else if(state === RESOLVED) {
|
||||||
|
processMessage(msg, send, done);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.on("close", function() {
|
node.on("close", function() {
|
||||||
|
if (finScript) {
|
||||||
|
try {
|
||||||
|
finScript.runInContext(context, finOpt);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
node.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
while (node.outstandingTimers.length > 0) {
|
while (node.outstandingTimers.length > 0) {
|
||||||
clearTimeout(node.outstandingTimers.pop());
|
clearTimeout(node.outstandingTimers.pop());
|
||||||
}
|
}
|
||||||
while (node.outstandingIntervals.length > 0) {
|
while (node.outstandingIntervals.length > 0) {
|
||||||
clearInterval(node.outstandingIntervals.pop());
|
clearInterval(node.outstandingIntervals.pop());
|
||||||
}
|
}
|
||||||
this.status({});
|
node.status({});
|
||||||
});
|
});
|
||||||
} catch(err) {
|
|
||||||
|
promise.then(function (v) {
|
||||||
|
var msgs = messages;
|
||||||
|
messages = [];
|
||||||
|
while (msgs.length > 0) {
|
||||||
|
msgs.forEach(function (s) {
|
||||||
|
processMessage(s.msg, s.send, s.done);
|
||||||
|
});
|
||||||
|
msgs = messages;
|
||||||
|
messages = [];
|
||||||
|
}
|
||||||
|
state = RESOLVED;
|
||||||
|
}).catch((error) => {
|
||||||
|
messages = [];
|
||||||
|
state = ERROR;
|
||||||
|
node.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
catch(err) {
|
||||||
// eg SyntaxError - which v8 doesn't include line number information
|
// eg SyntaxError - which v8 doesn't include line number information
|
||||||
// so we can't do better than this
|
// so we can't do better than this
|
||||||
this.error(err);
|
updateErrorInfo(err);
|
||||||
|
node.error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RED.nodes.registerType("function",FunctionNode);
|
RED.nodes.registerType("function",FunctionNode);
|
||||||
RED.library.register("functions");
|
RED.library.register("functions");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,12 +15,14 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script type="text/html" data-help-name="function">
|
<script type="text/html" data-help-name="function">
|
||||||
<p>A JavaScript function block to run against the messages being received by the node.</p>
|
<p>A JavaScript function to run against the messages being received by the node.</p>
|
||||||
<p>The messages are passed in as a JavaScript object called <code>msg</code>.</p>
|
<p>The messages are passed in as a JavaScript object called <code>msg</code>.</p>
|
||||||
<p>By convention it will have a <code>msg.payload</code> property containing
|
<p>By convention it will have a <code>msg.payload</code> property containing
|
||||||
the body of the message.</p>
|
the body of the message.</p>
|
||||||
<p>The function is expected to return a message object (or multiple message objects), but can choose
|
<p>The function is expected to return a message object (or multiple message objects), but can choose
|
||||||
to return nothing in order to halt a flow.</p>
|
to return nothing in order to halt a flow.</p>
|
||||||
|
<p>The <b>Setup</b> tab contains code that will be run whenever the node is started.
|
||||||
|
The <b>Close</b> tab contains code that will be run when the node is stopped.</p>
|
||||||
<h3>Details</h3>
|
<h3>Details</h3>
|
||||||
<p>See the <a target="_blank" href="http://nodered.org/docs/writing-functions.html">online documentation</a>
|
<p>See the <a target="_blank" href="http://nodered.org/docs/writing-functions.html">online documentation</a>
|
||||||
for more information on writing functions.</p>
|
for more information on writing functions.</p>
|
||||||
|
@ -210,8 +210,14 @@
|
|||||||
"function": "",
|
"function": "",
|
||||||
"label": {
|
"label": {
|
||||||
"function": "Function",
|
"function": "Function",
|
||||||
|
"initialize": "Setup",
|
||||||
|
"finalize": "Close",
|
||||||
"outputs": "Outputs"
|
"outputs": "Outputs"
|
||||||
},
|
},
|
||||||
|
"text": {
|
||||||
|
"initialize": "// Code added here will be run once\n// whenever the node is deployed.\n",
|
||||||
|
"finalize": "// Code added here will be run when the\n// node is being stopped or re-deployed.\n"
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"inputListener":"Cannot add listener to 'input' event within Function",
|
"inputListener":"Cannot add listener to 'input' event within Function",
|
||||||
"non-message-returned":"Function tried to send a message of type __type__"
|
"non-message-returned":"Function tried to send a message of type __type__"
|
||||||
|
@ -15,10 +15,11 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script type="text/html" data-help-name="function">
|
<script type="text/html" data-help-name="function">
|
||||||
<p>受信メッセージに対して処理を行うJavaScriptコード(関数の本体)を定義します。</p>
|
<p>受信メッセージに対して処理を行うJavaScriptコード(関数の本体)を<b>Function</b>タブに定義します。</p>
|
||||||
<p>入力メッセージは<code>msg</code>という名称のJavaScriptオブジェクトで受け渡されます。</p>
|
<p>入力メッセージは<code>msg</code>という名称のJavaScriptオブジェクトで受け渡されます。</p>
|
||||||
<p><code>msg</code>オブジェクトは<code>msg.payload</code>プロパティにメッセージ本体を保持するのが慣例です。</p>
|
<p><code>msg</code>オブジェクトは<code>msg.payload</code>プロパティにメッセージ本体を保持するのが慣例です。</p>
|
||||||
<p>通常、コードはメッセージオブジェクト(もしくは複数のメッセージオブジェクト)を返却します。後続フローの実行を停止したい場合は、オブジェクトを返却しなくてもかまいません。</p>
|
<p>通常、コードはメッセージオブジェクト(もしくは複数のメッセージオブジェクト)を返却します。後続フローの実行を停止したい場合は、オブジェクトを返却しなくてもかまいません。</p>
|
||||||
|
<p>Node-REDの開始時もしくはフローの設定をデプロイした際実行される初期化コードを<b>初期化処理</b>タブに、ノードの停止もしくは再デプロイ時に実行される終了処理コードを<b>終了処理</b>タブに指定できます。</p>
|
||||||
<h3>詳細</h3>
|
<h3>詳細</h3>
|
||||||
<p>コードの書き方の詳細については、<a target="_blank" href="http://nodered.org/docs/writing-functions.html">オンラインドキュメント</a>を参照してください。</p>
|
<p>コードの書き方の詳細については、<a target="_blank" href="http://nodered.org/docs/writing-functions.html">オンラインドキュメント</a>を参照してください。</p>
|
||||||
<h4>メッセージの送信</h4>
|
<h4>メッセージの送信</h4>
|
||||||
|
@ -208,8 +208,14 @@
|
|||||||
"function": "",
|
"function": "",
|
||||||
"label": {
|
"label": {
|
||||||
"function": "コード",
|
"function": "コード",
|
||||||
|
"initialize": "初期化処理",
|
||||||
|
"finalize": "終了処理",
|
||||||
"outputs": "出力数"
|
"outputs": "出力数"
|
||||||
},
|
},
|
||||||
|
"text": {
|
||||||
|
"initialize": "// ここのコードはノードのデプロイ時に一度実行されます。\n",
|
||||||
|
"finalize": "// ここのコードはノードの停止もしくは再デプロイ時に実行されます。\n"
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"inputListener": "コード内で'input'イベントのリスナを設定できません",
|
"inputListener": "コード内で'input'イベントのリスナを設定できません",
|
||||||
"non-message-returned": "Functionノードが __type__ 型のメッセージ送信を試みました"
|
"non-message-returned": "Functionノードが __type__ 型のメッセージ送信を試みました"
|
||||||
|
@ -25,6 +25,18 @@ var settings;
|
|||||||
var libDir;
|
var libDir;
|
||||||
var libFlowsDir;
|
var libFlowsDir;
|
||||||
|
|
||||||
|
function toSingleLine(text) {
|
||||||
|
var result = text.replace(/\\/g, "\\\\").replace(/\n/g, "\\n");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromSingleLine(text) {
|
||||||
|
var result = text.replace(/\\[\\n]/g, function(s) {
|
||||||
|
return ((s === "\\\\") ? "\\" : "\n");
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function getFileMeta(root, path) {
|
function getFileMeta(root, path) {
|
||||||
var fn = fspath.join(root, path);
|
var fn = fspath.join(root, path);
|
||||||
var fd = fs.openSync(fn, 'r');
|
var fd = fs.openSync(fn, 'r');
|
||||||
@ -43,7 +55,7 @@ function getFileMeta(root, path) {
|
|||||||
for (var i = 0; i < parts.length; i++) {
|
for (var i = 0; i < parts.length; i++) {
|
||||||
var match = /^\/\/ (\w+): (.*)/.exec(parts[i]);
|
var match = /^\/\/ (\w+): (.*)/.exec(parts[i]);
|
||||||
if (match) {
|
if (match) {
|
||||||
meta[match[1]] = match[2];
|
meta[match[1]] = fromSingleLine(match[2]);
|
||||||
} else {
|
} else {
|
||||||
read = size;
|
read = size;
|
||||||
break;
|
break;
|
||||||
@ -153,7 +165,7 @@ module.exports = {
|
|||||||
var headers = "";
|
var headers = "";
|
||||||
for (var i in meta) {
|
for (var i in meta) {
|
||||||
if (meta.hasOwnProperty(i)) {
|
if (meta.hasOwnProperty(i)) {
|
||||||
headers += "// "+i+": "+meta[i]+"\n";
|
headers += "// "+i+": "+toSingleLine(meta[i])+"\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === "flows" && settings.flowFilePretty) {
|
if (type === "flows" && settings.flowFilePretty) {
|
||||||
|
@ -53,7 +53,6 @@ describe('function node', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should be loaded', function(done) {
|
it('should be loaded', function(done) {
|
||||||
var flow = [{id:"n1", type:"function", name: "function" }];
|
var flow = [{id:"n1", type:"function", name: "function" }];
|
||||||
helper.load(functionNode, flow, function() {
|
helper.load(functionNode, flow, function() {
|
||||||
@ -1336,6 +1335,46 @@ describe('function node', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should execute initialization', function(done) {
|
||||||
|
var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload = global.get('X'); return msg;",initialize:"global.set('X','bar');"},
|
||||||
|
{id:"n2", type:"helper"}];
|
||||||
|
helper.load(functionNode, flow, function() {
|
||||||
|
var n1 = helper.getNode("n1");
|
||||||
|
var n2 = helper.getNode("n2");
|
||||||
|
n2.on("input", function(msg) {
|
||||||
|
msg.should.have.property("payload", "bar");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
n1.receive({payload: "foo"});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wait completion of initialization', function(done) {
|
||||||
|
var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload = global.get('X'); return msg;",initialize:"global.set('X', '-'); return new Promise((resolve, reject) => setTimeout(() => { global.set('X','bar'); resolve(); }, 500));"},
|
||||||
|
{id:"n2", type:"helper"}];
|
||||||
|
helper.load(functionNode, flow, function() {
|
||||||
|
var n1 = helper.getNode("n1");
|
||||||
|
var n2 = helper.getNode("n2");
|
||||||
|
n2.on("input", function(msg) {
|
||||||
|
msg.should.have.property("payload", "bar");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
n1.receive({payload: "foo"});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should execute finalization', function(done) {
|
||||||
|
var flow = [{id:"n1",type:"function",wires:[],func:"return msg;",finalize:"global.set('X','bar');"}];
|
||||||
|
helper.load(functionNode, flow, function() {
|
||||||
|
var n1 = helper.getNode("n1");
|
||||||
|
var ctx = n1.context().global;
|
||||||
|
helper.unload().then(function () {
|
||||||
|
ctx.get('X').should.equal("bar");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Logger', function () {
|
describe('Logger', function () {
|
||||||
it('should log an Info Message', function (done) {
|
it('should log an Info Message', function (done) {
|
||||||
var flow = [{id: "n1", type: "function", wires: [["n2"]], func: "node.log('test');"}];
|
var flow = [{id: "n1", type: "function", wires: [["n2"]], func: "node.log('test');"}];
|
||||||
|
Loading…
Reference in New Issue
Block a user