mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	add support of initialization & finalization to function node
This commit is contained in:
		| @@ -50,6 +50,18 @@ RED.library = (function() { | ||||
|         '</form>'+ | ||||
|     '</div>' | ||||
|  | ||||
|     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 saveToLibrary() { | ||||
|         var elementPrefix = activeLibrary.elementPrefix || "node-input-"; | ||||
|         var name = $("#"+elementPrefix+"name").val().trim(); | ||||
| @@ -68,6 +80,10 @@ RED.library = (function() { | ||||
|             var field = activeLibrary.fields[i]; | ||||
|             if (field == "name") { | ||||
|                 data.name = name; | ||||
|             } else if(field == "initialize") { | ||||
|                 data.initialize = toSingleLine(activeLibrary.initEditor.getValue()); | ||||
|             } else if(field == "finalize") { | ||||
|                 data.finalize = toSingleLine(activeLibrary.finalizeEditor.getValue()); | ||||
|             } else { | ||||
|                 data[field] = $("#"+elementPrefix+field).val(); | ||||
|             } | ||||
| @@ -523,7 +539,17 @@ RED.library = (function() { | ||||
|                                 var elementPrefix = activeLibrary.elementPrefix || "node-input-"; | ||||
|                                 for (var i=0; i<activeLibrary.fields.length; i++) { | ||||
|                                     var field = activeLibrary.fields[i]; | ||||
|                                     $("#"+elementPrefix+field).val(selectedLibraryItem[field]); | ||||
|                                     if (field === "initialize") { | ||||
|                                         var text = fromSingleLine(selectedLibraryItem.initialize); | ||||
|                                         activeLibrary.initEditor.setValue(text, -1); | ||||
|                                     } | ||||
|                                     else if (field === "finalize") { | ||||
|                                         var text = fromSingleLine(selectedLibraryItem.finalize); | ||||
|                                         activeLibrary.finalizeEditor.setValue(text, -1); | ||||
|                                     } | ||||
|                                     else { | ||||
|                                         $("#"+elementPrefix+field).val(selectedLibraryItem[field]); | ||||
|                                     } | ||||
|                                 } | ||||
|                                 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> | ||||
|         <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 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> | ||||
|         <input type="hidden" id="node-input-func" autofocus="autofocus"> | ||||
|         <input type="hidden" id="node-input-noerr"> | ||||
|  | ||||
|     <div class="form-row"> | ||||
|         <ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul> | ||||
|     </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: 250px; min-height:150px;" 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 id="func-tabs-content" style="min-height: calc(100% - 80px);"> | ||||
|  | ||||
|         <div id="func-tab-init" style="display:none"> | ||||
|             <div class="form-row" style="margin-bottom: 0px;"> | ||||
|                 <input type="hidden" id="node-input-initialize" autofocus="autofocus"> | ||||
|             </div> | ||||
|  | ||||
|             <div class="form-row node-text-editor-row" style="position:relative"> | ||||
|                 <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-init-editor" ></div> | ||||
|                 <div style="position: absolute; right:0; margin-top:5px;"><button id="node-init-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|         <div id="func-tab-code" 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="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-func-editor" ></div> | ||||
|                 <div style="position: absolute; right:0; margin-top:5px;"><button id="node-function-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></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="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-finalize-editor" ></div> | ||||
|                 <div style="position: absolute; right:0; margin-top:5px;"><button id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|     </div> | ||||
|  | ||||
| </script> | ||||
|  | ||||
| <script type="text/javascript"> | ||||
| @@ -27,7 +62,9 @@ | ||||
|             name: {value:""}, | ||||
|             func: {value:"\nreturn msg;"}, | ||||
|             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, | ||||
|         outputs:1, | ||||
| @@ -40,6 +77,28 @@ | ||||
|         }, | ||||
|         oneditprepare: function() { | ||||
|             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-code", | ||||
|                 label: that._("function.label.function") | ||||
|             }); | ||||
|             tabs.addTab({ | ||||
|                 id: "func-tab-finalize", | ||||
|                 label: that._("function.label.finalize") | ||||
|             }); | ||||
|             tabs.activateTab("func-tab-code"); | ||||
|  | ||||
|             $( "#node-input-outputs" ).spinner({ | ||||
|                 min:0, | ||||
|                 change: function(event, ui) { | ||||
| @@ -70,12 +129,54 @@ | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             this.initEditor = RED.editor.createEditor({ | ||||
|                 id: 'node-input-init-editor', | ||||
|                 mode: 'ace/mode/nrjavascript', | ||||
|                 value: $("#node-input-initialize").val(), | ||||
|                 globals: { | ||||
|                     msg:true, | ||||
|                     context:true, | ||||
|                     RED: true, | ||||
|                     util: true, | ||||
|                     flow: true, | ||||
|                     global: true, | ||||
|                     console: true, | ||||
|                     Buffer: true, | ||||
|                     setTimeout: true, | ||||
|                     clearTimeout: true, | ||||
|                     setInterval: true, | ||||
|                     clearInterval: true | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             this.finalizeEditor = RED.editor.createEditor({ | ||||
|                 id: 'node-input-finalize-editor', | ||||
|                 mode: 'ace/mode/nrjavascript', | ||||
|                 value: $("#node-input-finalize").val(), | ||||
|                 globals: { | ||||
|                     msg:true, | ||||
|                     context:true, | ||||
|                     RED: true, | ||||
|                     util: true, | ||||
|                     flow: true, | ||||
|                     global: true, | ||||
|                     console: true, | ||||
|                     Buffer: true, | ||||
|                     setTimeout: true, | ||||
|                     clearTimeout: true, | ||||
|                     setInterval: true, | ||||
|                     clearInterval: true | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             RED.library.create({ | ||||
|                 url:"functions", // where to get the data from | ||||
|                 type:"function", // the type of object the library is for | ||||
|                 editor:this.editor, // the field name the main text body goes to | ||||
|                 initEditor: this.initEditor, // editor for initializer | ||||
|                 finalizeEditor: this.finalizeEditor, // editor for finalizer | ||||
|                 mode:"ace/mode/nrjavascript", | ||||
|                 fields:['name','outputs'], | ||||
|                 fields:['name','outputs', 'initialize', 'finalize'], | ||||
|                 ext:"js" | ||||
|             }); | ||||
|             this.editor.focus(); | ||||
| @@ -98,26 +199,100 @@ | ||||
|                         },300); | ||||
|                     } | ||||
|                 }) | ||||
|             }) | ||||
|             }); | ||||
|  | ||||
|             $("#node-init-expand-js").on("click", function(e) { | ||||
|                 e.preventDefault(); | ||||
|                 var editor = that.initEditor; | ||||
|                 var value = editor.getValue(); | ||||
|                 RED.editor.editJavaScript({ | ||||
|                     value: value, | ||||
|                     width: "Infinity", | ||||
|                     cursor: editor.getCursorPosition(), | ||||
|                     mode: "ace/mode/nrjavascript", | ||||
|                     complete: function(v,cursor) { | ||||
|                         editor.setValue(v, -1); | ||||
|                         editor.gotoLine(cursor.row+1,cursor.column,false); | ||||
|                         setTimeout(function() { | ||||
|                             editor.focus(); | ||||
|                         },300); | ||||
|                     } | ||||
|                 }) | ||||
|             }); | ||||
|  | ||||
|             $("#node-finalize-expand-js").on("click", function(e) { | ||||
|                 e.preventDefault(); | ||||
|                 var editor = that.finalizeEditor; | ||||
|                 var value = editor.getValue(); | ||||
|                 RED.editor.editJavaScript({ | ||||
|                     value: value, | ||||
|                     width: "Infinity", | ||||
|                     cursor: editor.getCursorPosition(), | ||||
|                     mode: "ace/mode/nrjavascript", | ||||
|                     complete: function(v,cursor) { | ||||
|                         editor.setValue(v, -1); | ||||
|                         editor.gotoLine(cursor.row+1,cursor.column,false); | ||||
|                         setTimeout(function() { | ||||
|                             editor.focus(); | ||||
|                         },300); | ||||
|                     } | ||||
|                 }) | ||||
|             }); | ||||
|  | ||||
|              | ||||
|         }, | ||||
|         oneditsave: function() { | ||||
|             var node = this; | ||||
|             var noerr = 0; | ||||
|             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; | ||||
|                     noerr += annot.length; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             $("#node-input-func").val(this.editor.getValue()); | ||||
|             this.editor.destroy(); | ||||
|             delete this.editor; | ||||
|             var annotIni = this.initEditor.getSession().getAnnotations(); | ||||
|             for (var k=0; k < annotIni.length; k++) { | ||||
|                 if (annotIni[k].type === "error") { | ||||
|                     noerr += annotIni.length; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             var annotFin = this.finalizeEditor.getSession().getAnnotations(); | ||||
|             for (var k=0; k < annotFin.length; k++) { | ||||
|                 if (annotFin[k].type === "error") { | ||||
|                     noerr += annotFin.length; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             $("#node-input-noerr").val(noerr); | ||||
|             this.noerr = noerr; | ||||
|  | ||||
|             $("#node-input-func").val(node.editor.getValue()); | ||||
|             node.editor.destroy(); | ||||
|             delete node.editor; | ||||
|  | ||||
|             $("#node-input-initialize").val(node.initEditor.getValue()); | ||||
|             node.initEditor.destroy(); | ||||
|             delete node.initEditor; | ||||
|  | ||||
|             $("#node-input-finalize").val(node.finalizeEditor.getValue()); | ||||
|             node.finalizeEditor.destroy(); | ||||
|             delete node.finalizeEditor; | ||||
|         }, | ||||
|         oneditcancel: function() { | ||||
|             this.editor.destroy(); | ||||
|             delete this.editor; | ||||
|             var node = this; | ||||
|  | ||||
|             node.editor.destroy(); | ||||
|             delete node.editor; | ||||
|  | ||||
|             node.initEditor.destroy(); | ||||
|             delete node.initEditor; | ||||
|  | ||||
|             node.finalizeEditor.destroy(); | ||||
|             delete node.finalizeEditor; | ||||
|         }, | ||||
|         oneditresize: function(size) { | ||||
|             var rows = $("#dialog-form>div:not(.node-text-editor-row)"); | ||||
| @@ -129,6 +304,11 @@ | ||||
|             height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); | ||||
|             $(".node-text-editor").css("height",height+"px"); | ||||
|             this.editor.resize(); | ||||
|  | ||||
|             var height = size.height; | ||||
|             $("#node-input-init-editor").css("height", (height -100)+"px"); | ||||
|             $("#node-input-func-editor").css("height", (height -120)+"px"); | ||||
|             $("#node-input-finalize-editor").css("height", (height -100)+"px"); | ||||
|         } | ||||
|     }); | ||||
| </script> | ||||
|   | ||||
| @@ -62,6 +62,8 @@ module.exports = function(RED) { | ||||
|         var node = this; | ||||
|         this.name = n.name; | ||||
|         this.func = n.func; | ||||
|         this.ini = n.initialize; | ||||
|         this.fin = n.finalize; | ||||
|  | ||||
|         var handleNodeDoneCall = true; | ||||
|         // Check to see if the Function appears to call `node.done()`. If so, | ||||
| @@ -89,6 +91,8 @@ module.exports = function(RED) { | ||||
|                               "};\n"+ | ||||
|                               this.func+"\n"+ | ||||
|                            "})(msg,send,done);"; | ||||
|         var iniText = "(function () {\n"+this.ini +"\n})();"; | ||||
|         var finText = "(function () {\n"+this.fin +"\n})();"; | ||||
|         this.topic = n.topic; | ||||
|         this.outstandingTimers = []; | ||||
|         this.outstandingIntervals = []; | ||||
| @@ -229,6 +233,7 @@ module.exports = function(RED) { | ||||
|         } | ||||
|         var context = vm.createContext(sandbox); | ||||
|         try { | ||||
|             vm.runInContext(iniText, context); | ||||
|             this.script = vm.createScript(functionText, { | ||||
|                 filename: 'Function node:'+this.id+(this.name?' ['+this.name+']':''), // filename for stack traces | ||||
|                 displayErrors: true | ||||
| @@ -297,6 +302,7 @@ module.exports = function(RED) { | ||||
|                 } | ||||
|             }); | ||||
|             this.on("close", function() { | ||||
|                 vm.runInContext(finText, context); | ||||
|                 while (node.outstandingTimers.length > 0) { | ||||
|                     clearTimeout(node.outstandingTimers.pop()); | ||||
|                 } | ||||
|   | ||||
| @@ -15,12 +15,13 @@ | ||||
| --> | ||||
|  | ||||
| <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 block written in <b>Function</b> tab 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>By convention it will have a <code>msg.payload</code> property containing | ||||
|        the body of the message.</p> | ||||
|     <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> | ||||
|     <p>Setup code executed before deploy can be specified in <b>Setup</b> tab.  Also, cleanup code executed before flow shutdown can be specified in <b>Cleanup</b> tab.</p> | ||||
|     <h3>Details</h3> | ||||
|     <p>See the <a target="_blank" href="http://nodered.org/docs/writing-functions.html">online documentation</a> | ||||
|     for more information on writing functions.</p> | ||||
|   | ||||
| @@ -207,6 +207,8 @@ | ||||
|         "function": "", | ||||
|         "label": { | ||||
|             "function": "Function", | ||||
|             "initialize": "Setup", | ||||
|             "finalize": "Cleanup", | ||||
|             "outputs": "Outputs" | ||||
|         }, | ||||
|         "error": { | ||||
|   | ||||
| @@ -15,10 +15,11 @@ | ||||
| --> | ||||
|  | ||||
| <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>オブジェクトは<code>msg.payload</code>プロパティにメッセージ本体を保持するのが慣例です。</p> | ||||
|     <p>通常、コードはメッセージオブジェクト(もしくは複数のメッセージオブジェクト)を返却します。後続フローの実行を停止したい場合は、オブジェクトを返却しなくてもかまいません。</p> | ||||
|     <p>デプロイ前に実行すべき初期化コードを<b>初期化処理</b>タブに、フローの停止時に実行すべき終了処理コードを<b>終了処理</b>タブに指定できます。</p> | ||||
|     <h3>詳細</h3> | ||||
|     <p>コードの書き方の詳細については、<a target="_blank" href="http://nodered.org/docs/writing-functions.html">オンラインドキュメント</a>を参照してください。</p> | ||||
|     <h4>メッセージの送信</h4> | ||||
|   | ||||
| @@ -207,6 +207,8 @@ | ||||
|         "function": "", | ||||
|         "label": { | ||||
|             "function": "コード", | ||||
|             "initialize": "初期化処理", | ||||
|             "finalize": "終了処理", | ||||
|             "outputs": "出力数" | ||||
|         }, | ||||
|         "error": { | ||||
|   | ||||
| @@ -53,7 +53,6 @@ describe('function node', function() { | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|  | ||||
|     it('should be loaded', function(done) { | ||||
|         var flow = [{id:"n1", type:"function", name: "function" }]; | ||||
|         helper.load(functionNode, flow, function() { | ||||
| @@ -1336,6 +1335,32 @@ 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 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 () { | ||||
|         it('should log an Info Message', function (done) { | ||||
|             var flow = [{id: "n1", type: "function", wires: [["n2"]], func: "node.log('test');"}]; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user