diff --git a/packages/node_modules/@node-red/nodes/core/function/15-change.html b/packages/node_modules/@node-red/nodes/core/function/15-change.html index 13f6503ae..453752f71 100644 --- a/packages/node_modules/@node-red/nodes/core/function/15-change.html +++ b/packages/node_modules/@node-red/nodes/core/function/15-change.html @@ -109,9 +109,41 @@ var del = this._("change.action.delete"); var move = this._("change.action.move"); var to = this._("change.action.to"); + var toValueLabel = this._("change.action.toValue",to); var search = this._("change.action.search"); var replace = this._("change.action.replace"); var regex = this._("change.label.regex"); + var deepCopyLabel = this._("change.label.deepCopy"); + + function createPropertyValue(row2_1,row2_2) { + var propValInput = $('',{class:"node-input-rule-property-value",type:"text"}) + .appendTo(row2_1) + .typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']}); + + var dcLabel = $('').appendTo(row2_2); + var deepCopy = $('').appendTo(dcLabel) + $('').text(deepCopyLabel).appendTo(dcLabel) + + propValInput.on("change", function(evt,type,val) { + row2_2.toggle(type === "msg" || type === "flow" || type === "global" || type === "env"); + }) + return [propValInput, deepCopy]; + } + function createFromValue(row3_1) { + return $('',{class:"node-input-rule-property-search-value",type:"text"}) + .appendTo(row3_1) + .typedInput({default:'str',types:['msg','flow','global','str','re','num','bool','env']}); + } + function createToValue(row3_2) { + return $('',{class:"node-input-rule-property-replace-value",type:"text"}) + .appendTo(row3_2) + .typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','env']}); + } + function createMoveValue(row4) { + return $('',{class:"node-input-rule-property-move-value",type:"text"}) + .appendTo(row4) + .typedInput({default:'msg',types:['msg','flow','global']}); + } $('#node-input-rule-container').css('min-height','150px').css('min-width','450px').editableList({ addItem: function(container,i,opt) { @@ -139,10 +171,10 @@ whiteSpace: 'nowrap' }); let fragment = document.createDocumentFragment(); - var row1 = $('
',{style:"display:flex;"}).appendTo(fragment); - var row2 = $('
',{style:"display:flex;margin-top:8px;"}).appendTo(fragment); + var row1 = $('
',{style:"display:flex; align-items: baseline"}).appendTo(fragment); + var row2 = $('
',{style:"margin-top:8px;"}).appendTo(fragment); var row3 = $('
',{style:"margin-top:8px;"}).appendTo(fragment); - var row4 = $('
',{style:"display:flex;margin-top:8px;"}).appendTo(fragment); + var row4 = $('
',{style:"display:flex;margin-top:8px;align-items: baseline"}).appendTo(fragment); var selectField = $('',{class:"node-input-rule-property-value",type:"text"}) - .appendTo(row2) - .typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']}); - } + var row2_2 = $('
', {style:"margin-top: 4px;"}).appendTo(row2); - var row3_1 = $('
', {style:"display:flex;"}).appendTo(row3); + var row3_1 = $('
', {style:"display:flex;align-items: baseline"}).appendTo(row3); $('
',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) .text(search) .appendTo(row3_1); - function createFromValue() { - return $('',{class:"node-input-rule-property-search-value",type:"text"}) - .appendTo(row3_1) - .typedInput({default:'str',types:['msg','flow','global','str','re','num','bool','env']}); - } - - var row3_2 = $('
',{style:"display:flex;margin-top:8px;"}).appendTo(row3); + var row3_2 = $('
',{style:"display:flex;margin-top:8px;align-items: baseline"}).appendTo(row3); $('
',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) .text(replace) .appendTo(row3_2); - function createToValue() { - return $('',{class:"node-input-rule-property-replace-value",type:"text"}) - .appendTo(row3_2) - .typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','env']}); - } - $('
',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) .text(to) .appendTo(row4); - function createMoveValue() { - return $('',{class:"node-input-rule-property-move-value",type:"text"}) - .appendTo(row4) - .typedInput({default:'msg',types:['msg','flow','global']}); - } - let propertyValue = null; let fromValue = null; let toValue = null; @@ -218,7 +229,9 @@ if (type == "set") { if(!propertyValue) { - propertyValue = createPropertyValue(); + var parts = createPropertyValue(row2_1, row2_2); + propertyValue = parts[0]; + deepCopy = parts[1]; } propertyValue.typedInput('show'); row2.show(); @@ -226,11 +239,11 @@ row4.hide(); } else if (type == "change") { if(!fromValue) { - fromValue = createFromValue(); + fromValue = createFromValue(row3_1); } fromValue.typedInput('show'); if(!toValue) { - toValue = createToValue(); + toValue = createToValue(row3_2); } toValue.typedInput('show'); row2.hide(); @@ -242,7 +255,7 @@ row4.hide(); } else if (type == "move") { if(!moveValue) { - moveValue = createMoveValue(); + moveValue = createMoveValue(row4); } moveValue.typedInput('show'); row2.hide(); @@ -256,26 +269,29 @@ propertyName.typedInput('type',rule.pt); if (rule.t == "set") { if(!propertyValue) { - propertyValue = createPropertyValue(); + var parts = createPropertyValue(row2_1, row2_2); + propertyValue = parts[0]; + deepCopy = parts[1]; } propertyValue.typedInput('value',rule.to); propertyValue.typedInput('type',rule.tot); + deepCopy.prop("checked", !!rule.dc); } if (rule.t == "move") { if(!moveValue) { - moveValue = createMoveValue(); + moveValue = createMoveValue(row4); } moveValue.typedInput('value',rule.to); moveValue.typedInput('type',rule.tot); } if (rule.t == "change") { if(!fromValue) { - fromValue = createFromValue(); + fromValue = createFromValue(row3_1); } fromValue.typedInput('value',rule.from); fromValue.typedInput('type',rule.fromt); if (!toValue) { - toValue = createToValue(); + toValue = createToValue(row3_2); } toValue.typedInput('value',rule.to); toValue.typedInput('type',rule.tot); @@ -331,6 +347,9 @@ if (type === "set") { r.to = rule.find(".node-input-rule-property-value").typedInput('value'); r.tot = rule.find(".node-input-rule-property-value").typedInput('type'); + if (rule.find(".node-input-rule-property-deepCopy").prop("checked")) { + r.dc = true; + } } else if (type === "move") { r.to = rule.find(".node-input-rule-property-move-value").typedInput('value'); r.tot = rule.find(".node-input-rule-property-move-value").typedInput('type'); diff --git a/packages/node_modules/@node-red/nodes/core/function/15-change.js b/packages/node_modules/@node-red/nodes/core/function/15-change.js index 55b9f44e9..d177caec8 100644 --- a/packages/node_modules/@node-red/nodes/core/function/15-change.js +++ b/packages/node_modules/@node-red/nodes/core/function/15-change.js @@ -206,6 +206,9 @@ module.exports = function(RED) { node.error(err, msg); return done(undefined,null); } else { + if (rule.dc) { + value = RED.util.cloneMessage(value); + } getFromValue(msg,rule,(err,fromParts) => { if (err) { node.error(err, msg); 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 cbb344ed3..a20d7b8db 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 @@ -713,13 +713,15 @@ "delete": "delete __property__", "move": "move __property__", "changeCount": "change: __count__ rules", - "regex": "Use regular expressions" + "regex": "Use regular expressions", + "deepCopy": "Deep copy value" }, "action": { "set": "Set", "change": "Change", "delete": "Delete", "move": "Move", + "toValue": "to the value", "to": "to", "search": "Search for", "replace": "Replace with" diff --git a/test/nodes/core/function/15-change_spec.js b/test/nodes/core/function/15-change_spec.js index 06d1e3774..b8d7be03b 100644 --- a/test/nodes/core/function/15-change_spec.js +++ b/test/nodes/core/function/15-change_spec.js @@ -98,7 +98,7 @@ describe('change Node', function() { }); describe('#set' , function() { - + it('sets the value of the message property', function(done) { var flow = [{"id":"changeNode1","type":"change","action":"replace","property":"payload","from":"","to":"changed","reg":false,"name":"changeNode","wires":[["helperNode1"]]}, {id:"helperNode1", type:"helper", wires:[]}]; @@ -615,7 +615,6 @@ describe('change Node', function() { }); - it('changes the value using jsonata', function(done) { var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"set","p":"payload","to":"$length(payload)","tot":"jsonata"}],"name":"changeNode","wires":[["helperNode1"]]}, {id:"helperNode1", type:"helper", wires:[]}]; @@ -847,7 +846,36 @@ describe('change Node', function() { }); }) + it('deep copies the property if selected', function(done) { + var flow = [{"id":"changeNode1","type":"change","rules":[{"t":"set","p":"payload","pt":"msg","to":"source","tot":"msg","dc":true}],"name":"changeNode","wires":[["helperNode1"]]}, + {id:"helperNode1", type:"helper", wires:[]}]; + helper.load(changeNode, flow, function() { + var changeNode1 = helper.getNode("changeNode1"); + var helperNode1 = helper.getNode("helperNode1"); + helperNode1.on("input", function(msg) { + try { + // Check payload has been set to a clone of original object + // - the JSON should match + JSON.stringify(msg.payload).should.equal(JSON.stringify(originalObject)) + // - but they must be different objects + msg.payload.should.not.equal(originalObject); + + // Modify nested property of original object + originalObject.a.c = 3; + // Check that modification hasn't happened on cloned prop + msg.payload.a.should.not.have.property('c'); + + done(); + } catch(err) { + done(err); + } + }); + var originalObject = { a: { b: 2 } } + changeNode1.receive({source:originalObject}); + }); + + }) }); describe('#change', function() { it('changes the value of the message property', function(done) {