diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js index 00666e5b4..ea1938e5c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js @@ -18,7 +18,7 @@ /** * options: * - addButton : boolean|string - text for add label, default 'add' - * - buttons : array - list of custom buttons (objects with fields 'label', 'icon', 'title', 'click') + * - buttons : array - list of custom buttons (objects with fields 'id', 'label', 'icon', 'title', 'click') * - height : number|'auto' * - resize : function - called when list as a whole is resized * - resizeItem : function(item) - called to resize individual item @@ -94,7 +94,7 @@ } buttons.forEach(function(button) { - var element = $('') + var element = $('') .appendTo(that.topContainer) .on("click", function(evt) { evt.preventDefault(); @@ -103,6 +103,9 @@ } }); + if (button.id) { + element.attr("id", button.id); + } if (button.title) { element.attr("title", button.title); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js index 2e7cd8f03..b7e2325c5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ -RED.tray = (function() { + RED.tray = (function() { var stack = []; var editorStack; @@ -288,9 +288,9 @@ RED.tray = (function() { right: -(tray.tray.width()+10)+"px" }); setTimeout(function() { - if (tray.options.close) { - tray.options.close(); - } + try { + if (tray.options.close) { tray.options.close(); } + } catch (ex) { } tray.tray.remove(); if (stack.length > 0) { var oldTray = stack[stack.length-1]; diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.html b/packages/node_modules/@node-red/nodes/core/common/20-inject.html index 9bf91ad1e..8e40b8cd8 100644 --- a/packages/node_modules/@node-red/nodes/core/common/20-inject.html +++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.html @@ -162,7 +162,62 @@ height += 16; $("#node-input-property-container").editableList('height',height); } - + /** Retrieve editableList items (refactored for re-use in the form inject button)*/ + function getProps(el, legacy) { + var result = { + props: [] + } + el.each(function(i) { + var prop = $(this); + var p = { + p:prop.find(".node-input-prop-property-name").typedInput('value') + }; + if (p.p) { + p.v = prop.find(".node-input-prop-property-value").typedInput('value'); + p.vt = prop.find(".node-input-prop-property-value").typedInput('type'); + if(legacy) { + if (p.p === "payload") { // save payload to old "legacy" property + result.payloadType = p.vt; + result.payload = p.v; + delete p.v; + delete p.vt; + } else if (p.p === "topic" && p.vt === "str") { + result.topic = p.v; + delete p.v; + } + } + result.props.push(p); + } + }); + return result; + } + /** Perform inject, optionally sending a custom msg (refactored for re-use in the form inject button)*/ + function doInject(node, customMsg) { + var label = node._def.label.call(node); + if (label.length > 30) { + label = label.substring(0, 50) + "..."; + } + label = label.replace(/&/g, "&").replace(//g, ">"); + $.ajax({ + url: "inject/" + node.id, + type: "POST", + data: customMsg, + success: function (resp) { + RED.notify(node._("inject.success", { label: label }), { type: "success", id: "inject", timeout: 2000 }); + }, + error: function (jqXHR, textStatus, errorThrown) { + if (jqXHR.status == 404) { + RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.not-deployed") }), "error"); + } else if (jqXHR.status == 500) { + RED.notify(node._("common.notification.error", { message: node._("inject.errors.failed") }), "error"); + } else if (jqXHR.status == 0) { + RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.no-response") }), "error"); + } else { + RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.unexpected", { status: jqXHR.status, message: textStatus }) }), "error"); + } + } + }); + } RED.nodes.registerType('inject',{   category: 'common', color:"#a6bbcf", @@ -283,14 +338,15 @@ return this.name?"node_label_italic":""; }, oneditprepare: function() { - if (this.payloadType == null) { - if (this.payload == "") { - this.payloadType = "date"; + var node = this; + if (node.payloadType == null) { + if (node.payload == "") { + node.payloadType = "date"; } else { - this.payloadType = "str"; + node.payloadType = "str"; } - } else if (this.payloadType === 'string' || this.payloadType === 'none') { - this.payloadType = "str"; + } else if (node.payloadType === 'string' || node.payloadType === 'none') { + node.payloadType = "str"; } $("#inject-time-type-select").on("change", function() { @@ -345,17 +401,17 @@ }); var repeattype = "none"; - if (this.repeat != "" && this.repeat != 0) { + if (node.repeat != "" && node.repeat != 0) { repeattype = "interval"; var r = "s"; - var c = this.repeat; - if (this.repeat % 60 === 0) { r = "m"; c = c/60; } - if (this.repeat % 1440 === 0) { r = "h"; c = c/60; } + var c = node.repeat; + if (node.repeat % 60 === 0) { r = "m"; c = c/60; } + if (node.repeat % 1440 === 0) { r = "h"; c = c/60; } $("#inject-time-interval-count").val(c); $("#inject-time-interval-units").val(r); $("#inject-time-interval-days").prop("disabled","disabled"); - } else if (this.crontab) { - var cronparts = this.crontab.split(" "); + } else if (node.crontab) { + var cronparts = node.crontab.split(" "); var days = cronparts[4]; if (!isNaN(cronparts[0]) && !isNaN(cronparts[1])) { repeattype = "time"; @@ -431,7 +487,24 @@ /* */ - $('#node-input-property-container').css('min-height','120px').css('min-width','450px').editableList({ + var eList = $('#node-input-property-container').css('min-height','120px').css('min-width','450px'); + + eList.editableList({ + buttons: [ + { + id: "node-inject-test-inject-button", + label: node._("inject.injectNow"), + click: function(e) { + var items = eList.editableList('items'); + var result = getProps(items); + var m = {__user_inject_props__: []}; + if (result && result.props && result.props.length) { + m.__user_inject_props__ = result.props; + } + doInject(node, m); + } + } + ], addItem: function(container,i,opt) { var prop = opt; if (!prop.hasOwnProperty('p')) { @@ -465,33 +538,38 @@ removable: true, sortable: true }); + $('#node-inject-test-inject-button').css("float", "right").css("margin-right", "unset"); - if (!this.props) { + if (RED.nodes.subflow(node.z)) { + $('#node-inject-test-inject-button').attr("disabled",true); + } + + if (!node.props) { var payload = { p:'payload', - v: this.payload ? this.payload : '', - vt:this.payloadType ? this.payloadType : 'date' + v: node.payload ? node.payload : '', + vt:node.payloadType ? node.payloadType : 'date' }; var topic = { p:'topic', - v: this.topic ? this.topic : '', + v: node.topic ? node.topic : '', vt:'string' } - this.props = [payload,topic]; + node.props = [payload,topic]; } - for (var i=0; i 30) { - label = label.substring(0,50)+"..."; - } - label = label.replace(/&/g,"&").replace(//g,">"); - var node = this; - $.ajax({ - url: "inject/"+this.id, - type:"POST", - success: function(resp) { - RED.notify(node._("inject.success",{label:label}),{type:"success",id:"inject", timeout: 2000}); - }, - error: function(jqXHR,textStatus,errorThrown) { - if (jqXHR.status == 404) { - RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.not-deployed")}),"error"); - } else if (jqXHR.status == 500) { - RED.notify(node._("common.notification.error",{message:node._("inject.errors.failed")}),"error"); - } else if (jqXHR.status == 0) { - RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.no-response")}),"error"); - } else { - RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.unexpected",{status:jqXHR.status,message:textStatus})}),"error"); - } - } - }); + doInject(this); } }, oneditresize: resizeDialog diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.js b/packages/node_modules/@node-red/nodes/core/common/20-inject.js index 772e238a3..26be74bfa 100644 --- a/packages/node_modules/@node-red/nodes/core/common/20-inject.js +++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.js @@ -100,8 +100,12 @@ module.exports = function(RED) { this.on("input", function(msg, send, done) { var errors = []; - - this.props.forEach(p => { + var props = this.props; + if(msg.__user_inject_props__ && Array.isArray(msg.__user_inject_props__)) { + props = msg.__user_inject_props__; + } + delete msg.__user_inject_props__; + props.forEach(p => { var property = p.p; var value = p.v ? p.v : ''; var valueType = p.vt ? p.vt : 'str'; @@ -156,7 +160,11 @@ module.exports = function(RED) { var node = RED.nodes.getNode(req.params.id); if (node != null) { try { - node.receive(); + if (req.body && req.body.__user_inject_props__) { + node.receive(req.body); + } else { + node.receive(); + } res.sendStatus(200); } catch(err) { res.sendStatus(500); diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 6945ee8ae..e5108710b 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -32,6 +32,7 @@ }, "inject": { "inject": "inject", + "injectNow": "inject now", "repeat": "repeat = __repeat__", "crontab": "crontab = __crontab__", "stopped": "stopped", diff --git a/test/nodes/core/common/20-inject_spec.js b/test/nodes/core/common/20-inject_spec.js index ff5eb7f73..f760bb646 100644 --- a/test/nodes/core/common/20-inject_spec.js +++ b/test/nodes/core/common/20-inject_spec.js @@ -510,6 +510,36 @@ describe('inject node', function() { }); }); + + it('should inject custom properties in message', function (done) { + //n1: inject node with { topic:"static", payload:"static", bool1:true, str1:"1" } + var flow = [{id: "n1", type: "inject", props: [{p:"payload", v:"static", vt:"str"}, {p:"topic", v:"static", vt:"str"}, {p:"bool1", v:"true", vt:"bool"}, {p:"str1", v:"1", vt:"str"}], wires: [["n2"]], z: "flow"}, + {id: "n2", type: "helper"}]; + helper.load(injectNode, flow, function () { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function (msg) { + try { + msg.should.not.have.property("payload"); //payload removed + msg.should.have.property("topic", "t_override"); //changed value to t_override + msg.should.have.property("str1", 1);//changed type from str to num + msg.should.have.property("num1", 1);//new prop + msg.should.have.property("bool1", false);//changed value to false + done(); + } catch (err) { + done(err); + } + }); + n1.receive({ __user_inject_props__: [ + {p:"topic", v:"t_override", vt:"str"}, //change value to t_override + {p:"str1", v:"1", vt:"num"}, //change type + {p:"num1", v:"1", vt:"num"}, //new prop + {p:"bool1", v:"false", vt:"bool"}, //change value to false + ]}); + }); + }); + + it('should inject multiple properties using legacy props if needed', function (done) { var flow = [{id: "n1", type: "inject", payload:"123", payloadType:"num", topic:"foo", props: [{p:"topic", vt:"str"}, {p:"payload"}], wires: [["n2"]], z: "flow"}, {id: "n2", type: "helper"}]; @@ -592,6 +622,46 @@ describe('inject node', function() { }); }); + it('should inject custom properties in posted message', function(done) { + var flow = [{id:"n1", type:"inject", payloadType:"str", topic: "t4",payload:"hello", wires:[["n4"]] }, + { id:"n4", type:"helper"}]; + helper.load(injectNode, flow, function() { + var n4 = helper.getNode("n4"); + n4.on("input", function(msg) { + msg.should.not.have.property("payload"); //payload removed + msg.should.have.property("topic", "t_override"); //changed value to t_override + msg.should.have.property("str1", "1"); //injected prop + msg.should.have.property("num1", 1); //injected prop + msg.should.have.property("bool1", true); //injected prop + + helper.clearFlows().then(function() { + done(); + }); + }); + try { + helper.request() + .post('/inject/n1') + .send({ __user_inject_props__: [ + {p:"topic", v:"t_override", vt:"str"}, //change value to t_override + {p:"str1", v:"1", vt:"str"}, //new prop + {p:"num1", v:"1", vt:"num"}, //new prop + {p:"bool1", v:"true", vt:"bool"}, //new prop + ]}) + .expect(200).end(function(err) { + if (err) { + console.log(err); + return helper.clearFlows() + .then(function () { + done(err); + }); + } + }); + } catch(err) { + done(err); + } + }); + }); + it('should fail for invalid node', function(done) { helper.request().post('/inject/invalid').expect(404).end(done); });