This commit is contained in:
Dave Conway-Jones 2020-05-04 14:17:17 +01:00
commit df9d231389
No known key found for this signature in database
GPG Key ID: 302A6725C594817F
30 changed files with 732 additions and 232 deletions

View File

@ -1,3 +1,59 @@
#### 1.0.6: Maintenance Release
Runtime
- Update to JSONata 1.8.3
- #2536 Handle clone of null in utils
Editor
- Prevent button label wrapping in typedInput
- Handle error objects when reporting in palette manager
Nodes
- Inject: Revert to cron 1.7.2
- UDP: when reusing input socket honour the broadcast mode.
#### 1.0.5: Maintenance Release
Runtime
- #2500 Support for context stores using JSONata and evaluateNodeProperty()
- Add better handling of host-key-verify error with projects
- #2517 Handle false values in $env() properly
- #2514 Ensure complete node scope is remapped in subflows
- #2513 Flows/subflows must preinitialise their context objects
- Clear node.close timeout to avoid unnecessary work on restart
- #2532 Set flow.disabled when disabled property is false
- #2522 Ensure file context does not write 'undefined' to store
Editor
- #2489 Fix XPath in UI tests
- #2504 Fix paletteCategories order
- #2501 Add page objects for UI testing
- #2494 Check node props when deciding if pasted node can splice links
- #2521 Don't double-sanitize node name in debug sidebar
- #2519 German i18n updates
- #2523 Update nodeTabMap when replacing unknown nodes
- Update TypedInput to use flexbox and remove resizing code
- Handle nodes with no wires array
- Do not collapse whitespace in Debug string messages
Nodes
- File: Remove old legacy wording from file node info to stop confusing users.
- Join: Ensure join node handles missing buffer joiner when not in string mode
- Exec: make exec node logging consistent with itself. (only be verbose when in verbose mode)
- Trigger: reset default timeout value when switching away from wait for reset
- Join: Fix join to not crash on appending invalid types to buffer.
- MQTT out: Add warning if topic contains + or #
- #2502 WebSocket i18n update
- #2508 Add Japanese translation for join node
- TCP out: tidy up select of which rows to display
#### 1.0.4: Maintenance Release #### 1.0.4: Maintenance Release
Runtime Runtime

View File

@ -32,9 +32,9 @@
"clone": "2.1.2", "clone": "2.1.2",
"content-type": "1.0.4", "content-type": "1.0.4",
"cookie": "0.4.0", "cookie": "0.4.0",
"cookie-parser": "1.4.4", "cookie-parser": "1.4.5",
"cors": "2.8.5", "cors": "2.8.5",
"cron": "1.8.2", "cron": "1.7.2",
"denque": "1.4.1", "denque": "1.4.1",
"express": "4.17.1", "express": "4.17.1",
"express-session": "1.17.0", "express-session": "1.17.0",
@ -47,18 +47,18 @@
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "3.13.1", "js-yaml": "3.13.1",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.8.1", "jsonata": "1.8.3",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"media-typer": "1.1.0", "media-typer": "1.1.0",
"memorystore": "1.6.2", "memorystore": "1.6.2",
"mime": "2.4.4", "mime": "2.4.4",
"mqtt": "2.18.8", "mqtt": "2.18.8",
"multer": "1.4.2", "multer": "1.4.2",
"mustache": "4.0.0", "mustache": "4.0.1",
"node-red-node-rbe": "^0.2.6", "node-red-node-rbe": "^0.2.6",
"node-red-node-sentiment": "^0.1.6", "node-red-node-sentiment": "^0.1.6",
"node-red-node-tail": "^0.1.0", "node-red-node-tail": "^0.1.0",
"nopt": "4.0.1", "nopt": "4.0.3",
"oauth2orize": "1.11.0", "oauth2orize": "1.11.0",
"on-headers": "1.0.2", "on-headers": "1.0.2",
"passport": "0.4.1", "passport": "0.4.1",
@ -67,16 +67,16 @@
"raw-body": "2.4.1", "raw-body": "2.4.1",
"request": "2.88.0", "request": "2.88.0",
"semver": "6.3.0", "semver": "6.3.0",
"uglify-js": "3.8.0", "uglify-js": "3.8.1",
"when": "3.7.8", "when": "3.7.8",
"ws": "6.2.1", "ws": "6.2.1",
"xml2js": "0.4.23" "xml2js": "0.4.23"
}, },
"optionalDependencies": { "optionalDependencies": {
"bcrypt": "3.0.6" "bcrypt": "3.0.8"
}, },
"devDependencies": { "devDependencies": {
"marked": "0.8.0", "marked": "0.8.2",
"dompurify": "2.0.8", "dompurify": "2.0.8",
"grunt": "~1.0.4", "grunt": "~1.0.4",
"grunt-chmod": "~1.1.1", "grunt-chmod": "~1.1.1",

View File

@ -26,7 +26,7 @@
"express": "4.17.1", "express": "4.17.1",
"memorystore": "1.6.2", "memorystore": "1.6.2",
"mime": "2.4.4", "mime": "2.4.4",
"mustache": "4.0.0", "mustache": "4.0.1",
"oauth2orize": "1.11.0", "oauth2orize": "1.11.0",
"passport-http-bearer": "1.0.1", "passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2", "passport-oauth2-client-password": "0.1.2",

View File

@ -1224,7 +1224,7 @@ RED.nodes = (function() {
defaults: {}, defaults: {},
label: "unknown: "+n.type, label: "unknown: "+n.type,
labelStyle: "red-ui-flow-node-label-italic", labelStyle: "red-ui-flow-node-label-italic",
outputs: n.outputs||n.wires.length, outputs: n.outputs|| (n.wires && n.wires.length) || 0,
set: registry.getNodeSet("node-red/unknown") set: registry.getNodeSet("node-red/unknown")
} }
} else { } else {

View File

@ -737,11 +737,13 @@
this.optionExpandButton.shown = false; this.optionExpandButton.shown = false;
} }
if (this.optionSelectTrigger) { if (this.optionSelectTrigger) {
this.optionSelectTrigger.show(); this.optionSelectTrigger.css({"display":"inline-flex"});
if (!opt.hasValue) { if (!opt.hasValue) {
this.optionSelectTrigger.css({"flex-grow":1})
this.elementDiv.hide(); this.elementDiv.hide();
this.valueLabelContainer.hide(); this.valueLabelContainer.hide();
} else { } else {
this.optionSelectTrigger.css({"flex-grow":0})
this.elementDiv.show(); this.elementDiv.show();
this.valueLabelContainer.hide(); this.valueLabelContainer.hide();
} }

View File

@ -223,7 +223,11 @@ RED.palette.editor = (function() {
var setElements = nodeEntry.sets[setName]; var setElements = nodeEntry.sets[setName];
if (set.err) { if (set.err) {
errorCount++; errorCount++;
$("<li>").text(set.err).appendTo(nodeEntry.errorList); var errMessage = set.err;
if (set.err.message) {
errMessage = set.err.message;
}
$("<li>").text(errMessage).appendTo(nodeEntry.errorList);
} }
if (set.enabled) { if (set.enabled) {
activeTypeCount += set.types.length; activeTypeCount += set.types.length;

View File

@ -217,6 +217,10 @@
.red-ui-debug-msg-type-number { color: $debug-message-text-color-msg-type-number; }; .red-ui-debug-msg-type-number { color: $debug-message-text-color-msg-type-number; };
.red-ui-debug-msg-type-number-toggle { cursor: pointer;} .red-ui-debug-msg-type-number-toggle { cursor: pointer;}
.red-ui-debug-msg-type-string {
white-space: pre-wrap;
}
.red-ui-debug-msg-row { .red-ui-debug-msg-row {
display: block; display: block;
padding: 4px 2px 2px; padding: 4px 2px 2px;

View File

@ -760,7 +760,7 @@ button.red-ui-toggleButton.toggle {
.red-ui-typedInput-value-label,.red-ui-typedInput-option-label { .red-ui-typedInput-value-label,.red-ui-typedInput-option-label {
select,.placeholder-input { select,.placeholder-input {
margin: 3px; margin: 3px;
height: 26px; height: 24px;
width: calc(100% - 10px); width: calc(100% - 10px);
padding-left: 3px; padding-left: 3px;
} }

View File

@ -110,9 +110,9 @@ button.red-ui-typedInput-option-trigger
background: $form-button-background; background: $form-button-background;
height: 32px; height: 32px;
line-height: 30px; line-height: 30px;
min-width: 23px;
vertical-align: middle; vertical-align: middle;
color: $form-text-color; color: $form-text-color;
white-space: nowrap;
i.red-ui-typedInput-icon { i.red-ui-typedInput-icon {
margin-left: 1px; margin-left: 1px;
margin-right: 2px; margin-right: 2px;
@ -174,25 +174,21 @@ button.red-ui-typedInput-option-trigger {
padding: 0 0 0 0; padding: 0 0 0 0;
position:relative; position:relative;
flex-grow: 1; flex-grow: 1;
line-height: 32px;
display: inline-flex;
.red-ui-typedInput-option-label { .red-ui-typedInput-option-label {
background:$form-button-background; background:$form-button-background;
color: $form-text-color; color: $form-text-color;
position:absolute; flex-grow: 1;
left:0; padding: 0 0 0 8px;
right:23px; display:inline-block;
top: 0;
padding: 0 5px 0 8px;
i.red-ui-typedInput-icon {
margin-right: 4px;
}
} }
.red-ui-typedInput-option-caret { .red-ui-typedInput-option-caret {
top: 0; flex-grow: 0;
position: absolute; display:inline-block;
right: 0; width: 23px;
bottom: 0; text-align: center;
width: 17px; height: 100%;
padding-left: 5px;
&:before { &:before {
content:''; content:'';
display: inline-block; display: inline-block;

View File

@ -16,14 +16,12 @@
<script type="text/html" data-template-name="inject"> <script type="text/html" data-template-name="inject">
<div class="form-row"> <div class="form-row">
<label for="node-input-payload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-payload" style="width:70%"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
<input type="hidden" id="node-input-payloadType">
</div> </div>
<div class="form-row"> <div class="form-row node-input-property-container-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label> <ol id="node-input-property-container"></ol>
<input type="text" id="node-input-topic">
</div> </div>
<div class="form-row" id="node-once"> <div class="form-row" id="node-once">
@ -114,12 +112,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips" data-i18n="[html]inject.tip"></div>
</script> </script>
<style> <style>
.inject-time-row { .inject-time-row {
@ -160,36 +153,85 @@
color:"#a6bbcf", color:"#a6bbcf",
defaults: { defaults: {
name: {value:""}, name: {value:""},
topic: {value:""}, props:{value:[{p:"payload",v:"",vt:"date"},{p:"topic",v:"",vt:"str"}]},
payload: {value:"", validate: RED.validators.typedInput("payloadType")},
payloadType: {value:"date"},
repeat: {value:"", validate:function(v) { return ((v === "") || (RED.validators.number(v) && (v >= 0) && (v <= 2147483))) }}, repeat: {value:"", validate:function(v) { return ((v === "") || (RED.validators.number(v) && (v >= 0) && (v <= 2147483))) }},
crontab: {value:""}, crontab: {value:""},
once: {value:false}, once: {value:false},
onceDelay: {value:0.1} onceDelay: {value:0.1},
/* Legacy */
topic: {value:""},
payload: {value:"", validate: RED.validators.typedInput("payloadType")},
payloadType: {value:"date"},
/* */
}, },
icon: "inject.svg", icon: "inject.svg",
inputs:0, inputs:0,
outputs:1, outputs:1,
outputLabels: function(index) { outputLabels: function(index) {
var lab = this.payloadType; var lab = '';
if (lab === "json") {
try { // if only payload and topic - display payload type
lab = typeof JSON.parse(this.payload); // if only one property - show it's type
if (lab === "object") { // if more than one property (other than payload and topic) - show "x properties" where x is the number of properties.
if (Array.isArray(JSON.parse(this.payload))) { lab = "Array"; }
// this.props will not be an array for legacy inject nodes until they are re-deployed
if (Array.isArray(this.props)) {
var propertyCount = this.props.length;
var payloadProperty = this.props.find(p => p.p === 'payload');
var topicProperty = this.props.find(p => p.p === 'topic' && p.vt === 'str');
if (payloadProperty && topicProperty) {
lab = payloadProperty.vt;
} else if (propertyCount > 1){
lab = propertyCount + " " + this._("inject.label.properties");
} else if(propertyCount === 1){
lab = this.props[0].vt;
}
} else {
lab = this.payloadType;
if (lab === "json") {
try {
lab = typeof JSON.parse(this.payload);
if (lab === "object") {
if (Array.isArray(JSON.parse(this.payload))) { lab = "Array"; }
}
} catch(e) {
return this._("inject.label.invalid");
} }
} catch(e) { }
return this._("inject.label.invalid"); }
} }
var name = "inject.label."+lab; var name = "inject.label."+lab;
var label = this._(name); var label = this._(name);
if (name !== label) { if (name !== label) { lab = label; }
return label;
}
return lab; return lab;
}, },
label: function() { label: function() {
if (Array.isArray(this.props)) {
// find the payload & topic
var payloadProperty = this.props.find(p => p.p === 'payload');
var topicProperty = this.props.find(p => p.p === 'topic' && p.vt === 'str');
// If no payload/topic are found, use the first property instead
if(this.props[0]){
payloadProperty = payloadProperty === undefined ? this.props[0] : payloadProperty;
topicProperty = topicProperty === undefined ? {v: payloadProperty.p} : topicProperty; // if no topic, use the property name of the payload
}
var payload = payloadProperty === undefined ? "" : payloadProperty.v;
var payloadType = payloadProperty === undefined ? "str" : payloadProperty.vt;
var topic = topicProperty === undefined ? "" : topicProperty.v;
} else {
/* Legacy */
var payload = this.payload;
var payloadType = this.payloadType;
var topic = this.topic;
}
var suffix = ""; var suffix = "";
// if fire once then add small indication // if fire once then add small indication
if (this.once) { if (this.once) {
@ -201,27 +243,27 @@
} }
if (this.name) { if (this.name) {
return this.name+suffix; return this.name+suffix;
} else if (this.payloadType === "string" || } else if (payloadType === "string" ||
this.payloadType === "str" || payloadType === "str" ||
this.payloadType === "num" || payloadType === "num" ||
this.payloadType === "bool" || payloadType === "bool" ||
this.payloadType === "json") { payloadType === "json") {
if ((this.topic !== "") && ((this.topic.length + this.payload.length) <= 32)) { if ((topic !== "") && ((topic.length + payload.length) <= 32)) {
return this.topic + ":" + this.payload+suffix; return topic + ":" + payload+suffix;
} else if (this.payload.length > 0 && this.payload.length < 24) { } else if (payload.length > 0 && payload.length < 24) {
return this.payload+suffix; return payload+suffix;
} else { } else {
return this._("inject.inject")+suffix; return this._("inject.inject")+suffix;
} }
} else if (this.payloadType === 'date') { } else if (payloadType === 'date' || payloadType === 'bin' || payloadType === 'env') {
if ((this.topic !== "") && (this.topic.length <= 16)) { if ((topic !== "") && (topic.length <= 16)) {
return this.topic + ":" + this._("inject.timestamp")+suffix; return topic + ":" + this._(`inject.label.${payloadType}`)+suffix;
} else { } else {
return this._("inject.timestamp")+suffix; return this._(`inject.label.${payloadType}`)+suffix;
} }
} else if (this.payloadType === 'flow' || this.payloadType === 'global') { } else if (payloadType === 'flow' || payloadType === 'global') {
var key = RED.utils.parseContextKey(this.payload); var key = RED.utils.parseContextKey(payload);
return this.payloadType+"."+key.key+suffix; return payloadType+"."+key.key+suffix;
} else { } else {
return this._("inject.inject")+suffix; return this._("inject.inject")+suffix;
} }
@ -259,6 +301,10 @@
$("#node-once").hide(); $("#node-once").hide();
$("#node-input-once").prop('checked', false); $("#node-input-once").prop('checked', false);
} }
// Scroll down
var scrollDiv = $("#dialog-form").parent();
scrollDiv.scrollTop(scrollDiv.prop('scrollHeight'));
}); });
$("#node-input-once").on("change", function() { $("#node-input-once").on("change", function() {
@ -383,8 +429,117 @@
$("#inject-time-type-select").trigger("change"); $("#inject-time-type-select").trigger("change");
$("#inject-time-interval-time-start").trigger("change"); $("#inject-time-interval-time-start").trigger("change");
/* */
function resizeItem(item) {
var newWidth = item.width();
item.find('.node-input-prop-property-name').typedInput("width", '155px');
item.find('.node-input-prop-property-value').typedInput("width", `${newWidth - 180}px`);
}
$('#node-input-property-container').css('min-height','150px').css('min-width','450px').editableList({
addItem: function(container,i,opt) {
var prop = opt;
if (!prop.hasOwnProperty('p')) {
prop = {p:"",v:"",vt:"str"};
}
container.css({
overflow: 'hidden',
whiteSpace: 'nowrap'
});
var row = $('<div/>').appendTo(container);
var propertyName = $('<input/>',{class:"node-input-prop-property-name",type:"text"})
.appendTo(row)
.typedInput({types:['msg']});
$('<div/>',{style: 'display:inline-block; padding:0px 4px 0px 4px;'})
.text('=')
.appendTo(row);
var propertyValue = $('<input/>',{class:"node-input-prop-property-value",type:"text"})
.appendTo(row)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']});
propertyName.typedInput('value',prop.p);
propertyValue.typedInput('value',prop.v);
propertyValue.typedInput('type',prop.vt);
resizeItem(container);
},
resizeItem: resizeItem,
removable: true,
sortable: true
});
if (!this.props) {
var payload = {
p:'payload',
v: this.payload ? this.payload : '',
vt:this.payloadType ? this.payloadType : 'date'
};
var topic = {
p:'topic',
v: this.topic ? this.topic : '',
vt:'string'
}
this.props = [payload,topic];
}
for (var i=0; i<this.props.length; i++) {
var prop = this.props[i];
$("#node-input-property-container").editableList('addItem',prop);
}
/* Experimental paste object
* This allows you to copy an object to your clipboard from the debug and then paste it back into an inject node
*/
// $("#dialog-form").on('paste', function(e) {
// var pasteData = e.originalEvent.clipboardData.getData('text');
// try{
// var pasteObject = JSON.parse(pasteData);
// } catch(e){ }
//
// if(pasteObject){
// for(var p in pasteObject){
// if(p === '_msgid') continue;
// var v = pasteObject[p];
// var vt = 'json';
//
// // Remove existing property before adding to avoid duplicates
// $(`#node-input-property-container .node-input-prop-property-name[value=${p}]`).closest('.red-ui-editableList-item-content').parent().remove();
//
// if(typeof v === 'string'){
// vt = 'str';
// } else if (typeof v === "boolean") {
// vt = 'bool';
// } else if (!isNaN(v)) {
// vt = 'num';
// } else if(Array.isArray(v) && v.every(e => Number.isInteger(e) && e >= 0 && e <=255)) { // Fuzzy buffer detection
// vt = 'bin';
// v = JSON.stringify(pasteObject[p]);
// } else {
// vt = 'json';
// v = JSON.stringify(pasteObject[p]);
// }
//
// var prop = {p, v, vt};
// $("#node-input-property-container").editableList('addItem',prop);
// }
// }
// });
}, },
oneditsave: function() { oneditsave: function() {
/* Cleanup Legacy */
delete this.payload;
delete this.payloadType
delete this.topic
/* */
var repeat = ""; var repeat = "";
var crontab = ""; var crontab = "";
var type = $("#inject-time-type-select").val(); var type = $("#inject-time-type-select").val();
@ -474,6 +629,22 @@
$("#node-input-repeat").val(repeat); $("#node-input-repeat").val(repeat);
$("#node-input-crontab").val(crontab); $("#node-input-crontab").val(crontab);
/* Gather the injected properties of the msg object */
var props = $("#node-input-property-container").editableList('items');
var node = this;
node.props= [];
props.each(function(i) {
var prop = $(this);
var p = {
p:prop.find(".node-input-prop-property-name").typedInput('value')
};
p.v = prop.find(".node-input-prop-property-value").typedInput('value');
p.vt = prop.find(".node-input-prop-property-value").typedInput('type');
node.props.push(p);
});
}, },
button: { button: {
enabled: function() { enabled: function() {
@ -483,12 +654,7 @@
if (this.changed) { if (this.changed) {
return RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.undeployedChanges")}),"warning"); return RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.undeployedChanges")}),"warning");
} }
var payload = this.payload;
if ((this.payloadType === 'flow') ||
(this.payloadType === 'global')) {
var key = RED.utils.parseContextKey(payload);
payload = this.payloadType+"."+key.key;
}
var label = this._def.label.call(this); var label = this._def.label.call(this);
if (label.length > 30) { if (label.length > 30) {
label = label.substring(0,50)+"..."; label = label.substring(0,50)+"...";
@ -514,6 +680,17 @@
} }
}); });
} }
},
oneditresize: function(size) {
var rows = $("#dialog-form>div:not(.node-input-property-container-row):visible");
var height = size.height;
for (var i=0; i<rows.length; i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-input-property-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
height += 16;
$("#node-input-property-container").editableList('height',height);
} }
}); });

View File

@ -20,9 +20,23 @@ module.exports = function(RED) {
function InjectNode(n) { function InjectNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.topic = n.topic;
this.payload = n.payload; /* Handle legacy */
this.payloadType = n.payloadType; if(!Array.isArray(n.props)){
n.props = [];
n.props.push({
p:'payload',
v:n.payload,
vt:n.payloadType
});
n.props.push({
p:'topic',
v:n.topic,
vt:'str'
});
}
this.props = n.props;
this.repeat = n.repeat; this.repeat = n.repeat;
this.crontab = n.crontab; this.crontab = n.crontab;
this.once = n.once; this.once = n.once;
@ -62,34 +76,27 @@ module.exports = function(RED) {
node.repeaterSetup(); node.repeaterSetup();
} }
this.on("input",function(msg) { this.on("input", function(msg) {
msg.topic = this.topic; var errors = [];
if (this.payloadType !== 'flow' && this.payloadType !== 'global') {
try {
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
msg.payload = Date.now();
} else if (this.payloadType == null) {
msg.payload = this.payload;
} else if (this.payloadType === 'none') {
msg.payload = "";
} else {
msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg);
}
this.send(msg);
msg = null;
} catch(err) {
this.error(err,msg);
}
} else {
RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg, function(err,res) {
if (err) {
node.error(err,msg);
} else {
msg.payload = res;
node.send(msg);
}
}); this.props.forEach(p => {
var property = p.p;
var value = p.v ? p.v : '';
var valueType = p.vt ? p.vt : 'str';
if (!property) return;
try {
RED.util.setMessageProperty(msg,property,RED.util.evaluateNodeProperty(value, valueType, this, msg),true);
} catch (err) {
errors.push(err);
}
});
if (errors.length) {
node.error(errors.join('; '), msg);
} else {
node.send(msg);
} }
}); });
} }

View File

@ -1,5 +1,10 @@
<script type="text/html" data-template-name="change"> <script type="text/html" data-template-name="change">
<style>
ol#node-input-rule-container .red-ui-typedInput-container {
flex:1;
}
</style>
<div class="form-row"> <div class="form-row">
<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>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@ -76,11 +81,6 @@
var replace = this._("change.action.replace"); var replace = this._("change.action.replace");
var regex = this._("change.label.regex"); var regex = this._("change.label.regex");
function resizeRule(rule) {
var newWidth = rule.width();
rule.find('.red-ui-typedInput').typedInput("width",newWidth-130);
}
$('#node-input-rule-container').css('min-height','150px').css('min-width','450px').editableList({ $('#node-input-rule-container').css('min-height','150px').css('min-width','450px').editableList({
addItem: function(container,i,opt) { addItem: function(container,i,opt) {
var rule = opt; var rule = opt;
@ -106,10 +106,11 @@
overflow: 'hidden', overflow: 'hidden',
whiteSpace: 'nowrap' whiteSpace: 'nowrap'
}); });
var row1 = $('<div/>').appendTo(container); let fragment = document.createDocumentFragment();
var row2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container); var row1 = $('<div/>',{style:"display:flex;"}).appendTo(fragment);
var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container); var row2 = $('<div/>',{style:"display:flex;margin-top:8px;"}).appendTo(fragment);
var row4 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container); var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(fragment);
var row4 = $('<div/>',{style:"display:flex;margin-top:8px;"}).appendTo(fragment);
var selectField = $('<select/>',{class:"node-input-rule-type",style:"width:110px; margin-right:10px;"}).appendTo(row1); var selectField = $('<select/>',{class:"node-input-rule-type",style:"width:110px; margin-right:10px;"}).appendTo(row1);
var selectOptions = [{v:"set",l:set},{v:"change",l:change},{v:"delete",l:del},{v:"move",l:move}]; var selectOptions = [{v:"set",l:set},{v:"change",l:change},{v:"delete",l:del},{v:"move",l:move}];
@ -124,41 +125,82 @@
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) $('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(to) .text(to)
.appendTo(row2); .appendTo(row2);
var propertyValue = $('<input/>',{class:"node-input-rule-property-value",type:"text"})
function createPropertyValue() {
return $('<input/>',{class:"node-input-rule-property-value",type:"text"})
.appendTo(row2) .appendTo(row2)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']}); .typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']});
}
var row3_1 = $('<div/>').appendTo(row3); var row3_1 = $('<div/>', {style:"display:flex;"}).appendTo(row3);
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) $('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(search) .text(search)
.appendTo(row3_1); .appendTo(row3_1);
var fromValue = $('<input/>',{class:"node-input-rule-property-search-value",type:"text"})
function createFromValue() {
return $('<input/>',{class:"node-input-rule-property-search-value",type:"text"})
.appendTo(row3_1) .appendTo(row3_1)
.typedInput({default:'str',types:['msg','flow','global','str','re','num','bool','env']}); .typedInput({default:'str',types:['msg','flow','global','str','re','num','bool','env']});
}
var row3_2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3); var row3_2 = $('<div/>',{style:"display:flex;margin-top:8px;"}).appendTo(row3);
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) $('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(replace) .text(replace)
.appendTo(row3_2); .appendTo(row3_2);
var toValue = $('<input/>',{class:"node-input-rule-property-replace-value",type:"text"})
function createToValue() {
return $('<input/>',{class:"node-input-rule-property-replace-value",type:"text"})
.appendTo(row3_2) .appendTo(row3_2)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','env']}); .typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','env']});
}
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) $('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(to) .text(to)
.appendTo(row4); .appendTo(row4);
var moveValue = $('<input/>',{class:"node-input-rule-property-move-value",type:"text"})
function createMoveValue() {
return $('<input/>',{class:"node-input-rule-property-move-value",type:"text"})
.appendTo(row4) .appendTo(row4)
.typedInput({default:'msg',types:['msg','flow','global']}); .typedInput({default:'msg',types:['msg','flow','global']});
}
let propertyValue = null;
let fromValue = null;
let toValue = null;
let moveValue = null;
selectField.on("change", function() { selectField.on("change", function() {
var width = $("#node-input-rule-container").width();
var type = $(this).val(); var type = $(this).val();
if (propertyValue) {
propertyValue.typedInput('hide');
}
if (fromValue) {
fromValue.typedInput('hide');
}
if (toValue) {
toValue.typedInput('hide');
}
if (moveValue) {
moveValue.typedInput('hide');
}
if (type == "set") { if (type == "set") {
if(!propertyValue) {
propertyValue = createPropertyValue();
}
propertyValue.typedInput('show');
row2.show(); row2.show();
row3.hide(); row3.hide();
row4.hide(); row4.hide();
} else if (type == "change") { } else if (type == "change") {
if(!fromValue) {
fromValue = createFromValue();
}
fromValue.typedInput('show');
if(!toValue) {
toValue = createToValue();
}
toValue.typedInput('show');
row2.hide(); row2.hide();
row3.show(); row3.show();
row4.hide(); row4.hide();
@ -167,30 +209,48 @@
row3.hide(); row3.hide();
row4.hide(); row4.hide();
} else if (type == "move") { } else if (type == "move") {
if(!moveValue) {
moveValue = createMoveValue();
}
moveValue.typedInput('show');
row2.hide(); row2.hide();
row3.hide(); row3.hide();
row4.show(); row4.show();
} }
resizeRule(container);
}); });
selectField.val(rule.t); selectField.val(rule.t);
propertyName.typedInput('value',rule.p); propertyName.typedInput('value',rule.p);
propertyName.typedInput('type',rule.pt); propertyName.typedInput('type',rule.pt);
propertyValue.typedInput('value',rule.to); if (rule.t == "set") {
propertyValue.typedInput('type',rule.tot); if(!propertyValue) {
moveValue.typedInput('value',rule.to); propertyValue = createPropertyValue();
moveValue.typedInput('type',rule.tot); }
fromValue.typedInput('value',rule.from); propertyValue.typedInput('value',rule.to);
fromValue.typedInput('type',rule.fromt); propertyValue.typedInput('type',rule.tot);
toValue.typedInput('value',rule.to); }
toValue.typedInput('type',rule.tot); if (rule.t == "move") {
if(!moveValue) {
moveValue = createMoveValue();
}
moveValue.typedInput('value',rule.to);
moveValue.typedInput('type',rule.tot);
}
if (rule.t == "change") {
if(!fromValue) {
fromValue = createFromValue();
}
fromValue.typedInput('value',rule.from);
fromValue.typedInput('type',rule.fromt);
if (!toValue) {
toValue = createToValue();
}
toValue.typedInput('value',rule.to);
toValue.typedInput('type',rule.tot);
}
selectField.change(); selectField.change();
container[0].appendChild(fragment);
var newWidth = $("#node-input-rule-container").width();
resizeRule(container);
}, },
resizeItem: resizeRule,
removable: true, removable: true,
sortable: true sortable: true
}); });

View File

@ -47,6 +47,10 @@
<input type="hidden" id="node-input-op2type"> <input type="hidden" id="node-input-op2type">
<input style="width:70%" type="text" id="node-input-op2" placeholder="0"> <input style="width:70%" type="text" id="node-input-op2" placeholder="0">
</div> </div>
<div class="form-row">
<label></label>
<input type="checkbox" id="node-input-second" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;" for="node-input-second" data-i18n="trigger.second"></label>
</div>
<div class="form-row"> <div class="form-row">
<label data-i18n="trigger.label.reset" style="width:auto"></label> <label data-i18n="trigger.label.reset" style="width:auto"></label>
<div style="display:inline-block; width:70%;vertical-align:top"> <div style="display:inline-block; width:70%;vertical-align:top">
@ -58,10 +62,13 @@
<br/> <br/>
<div class="form-row"> <div class="form-row">
<label data-i18n="trigger.for" for="node-input-bytopic"></label> <label data-i18n="trigger.for" for="node-input-bytopic"></label>
<select id="node-input-bytopic"> <select id="node-input-bytopic" style="width:120px;">
<option value="all" data-i18n="trigger.alltopics"></option> <option value="all" data-i18n="trigger.alltopics"></option>
<option value="topic" data-i18n="trigger.bytopics"></option> <option value="topic" data-i18n="trigger.bytopics"></option>
</select> </select>
<span class="form-row" id="node-stream-topic">
<input type="text" id="node-input-topic" style="width:46%;"/>
</span>
</div> </div>
<div class="form-row"> <div class="form-row">
<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>
@ -74,6 +81,7 @@
category: 'function', category: 'function',
color:"#E6E0F8", color:"#E6E0F8",
defaults: { defaults: {
name: {value:""},
op1: {value:"1", validate: RED.validators.typedInput("op1type")}, op1: {value:"1", validate: RED.validators.typedInput("op1type")},
op2: {value:"0", validate: RED.validators.typedInput("op2type")}, op2: {value:"0", validate: RED.validators.typedInput("op2type")},
op1type: {value:"val"}, op1type: {value:"val"},
@ -82,8 +90,9 @@
extend: {value:"false"}, extend: {value:"false"},
units: {value:"ms"}, units: {value:"ms"},
reset: {value:""}, reset: {value:""},
bytopic: {value: "all"}, bytopic: {value:"all"},
name: {value:""} topic: {value:"topic",required:true},
outputs: {value:1}
}, },
inputs:1, inputs:1,
outputs:1, outputs:1,
@ -103,6 +112,28 @@
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
}, },
oneditprepare: function() { oneditprepare: function() {
var that = this;
if (this.topic === undefined) { $("#node-input-topic").val("topic"); }
$("#node-input-topic").typedInput({default:'msg',types:['msg']});
$("#node-input-bytopic").on("change", function() {
if ($("#node-input-bytopic").val() === "all") {
$("#node-stream-topic").hide();
} else {
$("#node-stream-topic").show();
}
});
if (this.outputs == 2) { $("#node-input-second").prop('checked', true) }
else { $("#node-input-second").prop('checked', false) }
$("#node-input-second").change(function() {
if ($("#node-input-second").is(":checked")) {
that.outputs = 2;
}
else {
that.outputs = 1;
}
});
$("#node-then-type").on("change", function() { $("#node-then-type").on("change", function() {
if ($(this).val() == "block") { if ($(this).val() == "block") {
$(".node-type-wait").hide(); $(".node-type-wait").hide();
@ -177,7 +208,7 @@
} }
if ($("#node-then-type").val() == "loop") { if ($("#node-then-type").val() == "loop") {
$("#node-input-duration").val($("#node-input-duration").val() * -1); $("#node-input-duration").val($("#node-input-duration").val() * -1);
} }
} }
}); });
</script> </script>

View File

@ -24,6 +24,8 @@ module.exports = function(RED) {
this.op2 = n.op2 || "0"; this.op2 = n.op2 || "0";
this.op1type = n.op1type || "str"; this.op1type = n.op1type || "str";
this.op2type = n.op2type || "str"; this.op2type = n.op2type || "str";
this.second = (n.outputs == 2) ? true : false;
this.topic = n.topic || "topic";
if (this.op1type === 'val') { if (this.op1type === 'val') {
if (this.op1 === 'true' || this.op1 === 'false') { if (this.op1 === 'true' || this.op1 === 'false') {
@ -112,7 +114,7 @@ module.exports = function(RED) {
}); });
var processMessage = function(msg) { var processMessage = function(msg) {
var topic = msg.topic || "_none"; var topic = RED.util.getMessageProperty(msg,node.topic) || "_none";
var promise; var promise;
if (node.bytopic === "all") { topic = "_none"; } if (node.bytopic === "all") { topic = "_none"; }
node.topics[topic] = node.topics[topic] || {}; node.topics[topic] = node.topics[topic] || {};
@ -197,6 +199,9 @@ module.exports = function(RED) {
node.send(msg2); node.send(msg2);
} }
delete node.topics[topic]; delete node.topics[topic];
if (node.second === true) { node.send([null,msg2]); }
else { node.send(msg2); }
node.status({}); node.status({});
}).catch(err => { }).catch(err => {
node.error(err); node.error(err);
@ -246,7 +251,8 @@ module.exports = function(RED) {
} }
delete node.topics[topic]; delete node.topics[topic];
node.status({}); node.status({});
node.send(msg2); if (node.second === true) { node.send([null,msg2]); }
else { node.send(msg2); }
}).catch(err => { }).catch(err => {
node.error(err); node.error(err);
}); });

View File

@ -170,15 +170,14 @@
$("#node-input-port-row").hide(); $("#node-input-port-row").hide();
$("#node-input-host-row").hide(); $("#node-input-host-row").hide();
$("#node-input-end-row").hide(); $("#node-input-end-row").hide();
} else if (sockettype == "client"){
$("#node-input-port-row").show();
$("#node-input-host-row").show();
$("#node-input-end-row").show();
} else { } else {
$("#node-input-port-row").show(); $("#node-input-port-row").show();
$("#node-input-end-row").show();
}
if (sockettype == "client") {
$("#node-input-host-row").show();
} else {
$("#node-input-host-row").hide(); $("#node-input-host-row").hide();
$("#node-input-end-row").show();
} }
}; };
updateOptions(); updateOptions();

View File

@ -180,6 +180,10 @@ module.exports = function(RED) {
node.tout = setTimeout(function() { node.tout = setTimeout(function() {
if (udpInputPortsInUse[p]) { if (udpInputPortsInUse[p]) {
sock = udpInputPortsInUse[p]; sock = udpInputPortsInUse[p];
if (node.multicast != "false") {
sock.setBroadcast(true);
sock.setMulticastLoopback(false);
}
node.log(RED._("udp.status.re-use",{outport:node.outport,host:node.addr,port:node.port})); node.log(RED._("udp.status.re-use",{outport:node.outport,host:node.addr,port:node.port}));
if (node.iface) { node.status({text:n.iface+" : "+node.iface}); } if (node.iface) { node.status({text:n.iface+" : "+node.iface}); }
} }

View File

@ -28,7 +28,7 @@
</div> </div>
<div class="form-row" style="padding-left:20px;"> <div class="form-row" style="padding-left:20px;">
<label><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.input"></span></label> <label><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.input"></span></label>
<span data-i18n="csv.label.skip-s"></span>&nbsp;<input type="text" id="node-input-skip" style="width:30px; height:25px;"/>&nbsp;<span data-i18n="csv.label.skip-e"></span><br/> <span data-i18n="csv.label.skip-s"></span>&nbsp;<input type="text" id="node-input-skip" style="width:40px; height:25px;"/>&nbsp;<span data-i18n="csv.label.skip-e"></span><br/>
<label>&nbsp;</label> <label>&nbsp;</label>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-hdrin"><label style="width:auto; margin-top:7px;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span></label><br/> <input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-hdrin"><label style="width:auto; margin-top:7px;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span></label><br/>
<label>&nbsp;</label> <label>&nbsp;</label>
@ -49,8 +49,13 @@
<label style="width:100%;"><span data-i18n="csv.label.o2c"></span></label> <label style="width:100%;"><span data-i18n="csv.label.o2c"></span></label>
</div> </div>
<div class="form-row" style="padding-left:20px;"> <div class="form-row" style="padding-left:20px;">
<label><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.output"></span></label> <label><i class="fa fa-sign-out"></i> <span data-i18n="csv.label.output"></span></label>
<input style="width:20px; vertical-align:top; margin-right:5px;" type="checkbox" id="node-input-hdrout"><label style="width:auto;" for="node-input-hdrout"><span data-i18n="csv.label.includerow"></span></span> <!-- <input style="width:20px; vertical-align:top; margin-right:5px;" type="checkbox" id="node-input-hdrout"><label style="width:auto;" for="node-input-hdrout"><span data-i18n="csv.label.includerow"></span></span> -->
<select style="width:60%" id="node-input-hdrout">
<option value="none" data-i18n="csv.hdrout.none"></option>
<option value="all" data-i18n="csv.hdrout.all"></option>
<option value="once" data-i18n="csv.hdrout.once"></option>
</select>
</div> </div>
<div class="form-row" style="padding-left:20px;"> <div class="form-row" style="padding-left:20px;">
<label></label> <label></label>
@ -73,7 +78,7 @@
sep: {value:',',required:true,validate:RED.validators.regex(/^.{1,2}$/)}, sep: {value:',',required:true,validate:RED.validators.regex(/^.{1,2}$/)},
//quo: {value:'"',required:true}, //quo: {value:'"',required:true},
hdrin: {value:""}, hdrin: {value:""},
hdrout: {value:""}, hdrout: {value:"none"},
multi: {value:"one",required:true}, multi: {value:"one",required:true},
ret: {value:'\\n'}, ret: {value:'\\n'},
temp: {value:""}, temp: {value:""},
@ -92,6 +97,8 @@
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
}, },
oneditprepare: function() { oneditprepare: function() {
if (this.hdrout === false) { this.hdrout = "none"; $("#node-input-hdrout").val("none"); }
if (this.hdrout === true) { this.hdrout = "all"; $("#node-input-hdrout").val("all");}
if (this.strings === undefined) { this.strings = true; $("#node-input-strings").prop('checked', true); } if (this.strings === undefined) { this.strings = true; $("#node-input-strings").prop('checked', true); }
if (this.skip === undefined) { this.skip = 0; $("#node-input-skip").val("0");} if (this.skip === undefined) { this.skip = 0; $("#node-input-skip").val("0");}
$("#node-input-skip").spinner({ min:0 }); $("#node-input-skip").spinner({ min:0 });

View File

@ -26,7 +26,7 @@ module.exports = function(RED) {
this.lineend = "\n"; this.lineend = "\n";
this.multi = n.multi || "one"; this.multi = n.multi || "one";
this.hdrin = n.hdrin || false; this.hdrin = n.hdrin || false;
this.hdrout = n.hdrout || false; this.hdrout = n.hdrout || "none";
this.goodtmpl = true; this.goodtmpl = true;
this.skip = parseInt(n.skip || 0); this.skip = parseInt(n.skip || 0);
this.store = []; this.store = [];
@ -34,6 +34,8 @@ module.exports = function(RED) {
this.include_empty_strings = n.include_empty_strings || false; this.include_empty_strings = n.include_empty_strings || false;
this.include_null_values = n.include_null_values || false; this.include_null_values = n.include_null_values || false;
if (this.parsestrings === undefined) { this.parsestrings = true; } if (this.parsestrings === undefined) { this.parsestrings = true; }
if (this.hdrout === false) { this.hdrout = "none"; }
if (this.hdrout === true) { this.hdrout = "all"; }
var tmpwarn = true; var tmpwarn = true;
var node = this; var node = this;
@ -51,14 +53,22 @@ module.exports = function(RED) {
return col; return col;
} }
node.template = clean(node.template); node.template = clean(node.template);
node.hdrSent = false;
this.on("input", function(msg) { this.on("input", function(msg) {
if (msg.hasOwnProperty("reset")) {
node.hdrSent = false;
}
if (msg.hasOwnProperty("payload")) { if (msg.hasOwnProperty("payload")) {
if (typeof msg.payload == "object") { // convert object to CSV string if (typeof msg.payload == "object") { // convert object to CSV string
try { try {
var ou = ""; var ou = "";
if (node.hdrout) { if (node.hdrout !== "none" && node.hdrSent === false) {
if ((node.template.length === 1) && (node.template[0] === '') && (msg.hasOwnProperty("columns"))) {
node.template = clean((msg.columns || "").split(","));
}
ou += node.template.join(node.sep) + node.ret; ou += node.template.join(node.sep) + node.ret;
if (node.hdrout === "once") { node.hdrSent = true; }
} }
if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; } if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; }
for (var s = 0; s < msg.payload.length; s++) { for (var s = 0; s < msg.payload.length; s++) {
@ -77,13 +87,15 @@ module.exports = function(RED) {
ou += msg.payload[s].join(node.sep) + node.ret; ou += msg.payload[s].join(node.sep) + node.ret;
} }
else { else {
if ((node.template.length === 1) && (node.template[0] === '') && (msg.hasOwnProperty("columns"))) {
node.template = clean((msg.columns || "").split(","));
}
if ((node.template.length === 1) && (node.template[0] === '')) { if ((node.template.length === 1) && (node.template[0] === '')) {
/* istanbul ignore else */ /* istanbul ignore else */
if (tmpwarn === true) { // just warn about missing template once if (tmpwarn === true) { // just warn about missing template once
node.warn(RED._("csv.errors.obj_csv")); node.warn(RED._("csv.errors.obj_csv"));
tmpwarn = false; tmpwarn = false;
} }
ou = "";
for (var p in msg.payload[0]) { for (var p in msg.payload[0]) {
/* istanbul ignore else */ /* istanbul ignore else */
if (msg.payload[0].hasOwnProperty(p)) { if (msg.payload[0].hasOwnProperty(p)) {
@ -127,6 +139,7 @@ module.exports = function(RED) {
} }
} }
msg.payload = ou; msg.payload = ou;
msg.columns = node.template.join(',');
if (msg.payload !== '') { node.send(msg); } if (msg.payload !== '') { node.send(msg); }
} }
catch(e) { node.error(e,msg); } catch(e) { node.error(e,msg); }
@ -227,6 +240,7 @@ module.exports = function(RED) {
a.push(o); // add to the array a.push(o); // add to the array
} }
var has_parts = msg.hasOwnProperty("parts"); var has_parts = msg.hasOwnProperty("parts");
if (node.multi !== "one") { if (node.multi !== "one") {
msg.payload = a; msg.payload = a;
if (has_parts) { if (has_parts) {
@ -235,12 +249,14 @@ module.exports = function(RED) {
} }
if (msg.parts.index + 1 === msg.parts.count) { if (msg.parts.index + 1 === msg.parts.count) {
msg.payload = node.store; msg.payload = node.store;
msg.columns = node.template.filter(val => val).join(',');
delete msg.parts; delete msg.parts;
node.send(msg); node.send(msg);
node.store = []; node.store = [];
} }
} }
else { else {
msg.columns = node.template.filter(val => val).join(',');
node.send(msg); // finally send the array node.send(msg); // finally send the array
} }
} }
@ -248,6 +264,7 @@ module.exports = function(RED) {
var len = a.length; var len = a.length;
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
var newMessage = RED.util.cloneMessage(msg); var newMessage = RED.util.cloneMessage(msg);
newMessage.columns = node.template.filter(val => val).join(',');
newMessage.payload = a[i]; newMessage.payload = a[i];
if (!has_parts) { if (!has_parts) {
newMessage.parts = { newMessage.parts = {
@ -273,7 +290,11 @@ module.exports = function(RED) {
} }
else { node.warn(RED._("csv.errors.csv_js")); } else { node.warn(RED._("csv.errors.csv_js")); }
} }
else { node.send(msg); } // If no payload - just pass it on. else {
if (!msg.hasOwnProperty("reset")) {
node.send(msg); // If no payload and not reset - just pass it on.
}
}
}); });
} }
RED.nodes.registerType("csv",CSVNode); RED.nodes.registerType("csv",CSVNode);

View File

@ -40,6 +40,6 @@
progress will be cleared and no message triggered.</p> progress will be cleared and no message triggered.</p>
<p>The node can be configured to resend a message at a regular interval until it <p>The node can be configured to resend a message at a regular interval until it
is reset by a received message.</p> is reset by a received message.</p>
<p>Optionally, the node can be configured to treat messages with <code>msg.topic</code> as if they <p>Optionally, the node can be configured to treat messages as if they are separate streams,
are separate streams.</p> using a msg property to identify each stream. Default <code>msg.topic</code>.</p>
</script> </script>

View File

@ -37,6 +37,7 @@
"stopped": "stopped", "stopped": "stopped",
"failed": "Inject failed: __error__", "failed": "Inject failed: __error__",
"label": { "label": {
"properties": "Properties",
"repeat": "Repeat", "repeat": "Repeat",
"flow": "flow context", "flow": "flow context",
"global": "global context", "global": "global context",
@ -79,7 +80,6 @@
"on": "on", "on": "on",
"onstart": "Inject once after", "onstart": "Inject once after",
"onceDelay": "seconds, then", "onceDelay": "seconds, then",
"tip": "<b>Note:</b> \"interval between times\" and \"at a specific time\" will use cron.<br/>\"interval\" should be 596 hours or less.<br/>See info box for details.",
"success": "Successfully injected: __label__", "success": "Successfully injected: __label__",
"errors": { "errors": {
"failed": "inject failed, see log for details", "failed": "inject failed, see log for details",
@ -302,7 +302,7 @@
"wait-for": "wait for", "wait-for": "wait for",
"wait-loop": "resend it every", "wait-loop": "resend it every",
"for": "Handling", "for": "Handling",
"bytopics": "each msg.topic independently", "bytopics": "each",
"alltopics": "all messages", "alltopics": "all messages",
"duration": { "duration": {
"ms": "Milliseconds", "ms": "Milliseconds",
@ -311,6 +311,7 @@
"h": "Hours" "h": "Hours"
}, },
"extend": " extend delay if new message arrives", "extend": " extend delay if new message arrives",
"second": " send second message to separate output",
"label": { "label": {
"trigger": "trigger", "trigger": "trigger",
"trigger-block": "trigger & block", "trigger-block": "trigger & block",
@ -723,6 +724,11 @@
"mac": "Mac (\\r)", "mac": "Mac (\\r)",
"windows": "Windows (\\r\\n)" "windows": "Windows (\\r\\n)"
}, },
"hdrout": {
"none": "never send column headers",
"all": "always send column headers",
"once": "send headers once, until msg.reset"
},
"errors": { "errors": {
"csv_js": "This node only handles CSV strings or js objects.", "csv_js": "This node only handles CSV strings or js objects.",
"obj_csv": "No columns template specified for object -> CSV.", "obj_csv": "No columns template specified for object -> CSV.",

View File

@ -38,11 +38,13 @@
<p>The column template can contain an ordered list of column names. When converting CSV to an object, the column names <p>The column template can contain an ordered list of column names. When converting CSV to an object, the column names
will be used as the property names. Alternatively, the column names can be taken from the first row of the CSV.</p> will be used as the property names. Alternatively, the column names can be taken from the first row of the CSV.</p>
<p>When converting to CSV, the column template is used to identify which properties to extract from the object and in what order.</p> <p>When converting to CSV, the column template is used to identify which properties to extract from the object and in what order.</p>
<p>If the template is blank then the node can use a simple comma separated list of properties supplied in <code>msg.columns</code> to
determine what to extract. If that is not present then all the object properties are ouput in the order in which they are found.</p>
<p>If the input is an array then the columns template is only used to optionally generate a row of column titles.</p> <p>If the input is an array then the columns template is only used to optionally generate a row of column titles.</p>
<p>If 'parse numerical values' option is checked, string numerical values will be returned as numbers, ie. middle value '1,"1.5",2'.</p> <p>If 'parse numerical values' option is checked, string numerical values will be returned as numbers, ie. middle value '1,"1.5",2'.</p>
<p>If 'include empty strings' option is checked, empty strings will be returned in result, ie. middle value '"1","",3'.</p> <p>If 'include empty strings' option is checked, empty strings will be returned in result, ie. middle value '"1","",3'.</p>
<p>If 'include null values' option is checked, null values will be returned in result, ie. middle value '"1",,3'.</p> <p>If 'include null values' option is checked, null values will be returned in result, ie. middle value '"1",,3'.</p>
<p>The node can accept a multi-part input as long as the <code>parts</code> property is set correctly.</p> <p>The node can accept a multi-part input as long as the <code>parts</code> property is set correctly, for example from a file-in node or split node.</p>
<p>If outputting multiple messages they will have their <code>parts</code> property set and form a complete message sequence.</p> <p>If outputting multiple messages they will have their <code>parts</code> property set and form a complete message sequence.</p>
<p><b>Note:</b> the column template must be comma separated - even if a different separator is chosen for the data.</p> <p><b>Note:</b> the column template must be comma separated - even if a different separator is chosen for the data.</p>
</script> </script>

View File

@ -302,7 +302,7 @@
"wait-for": "指定した時間待機", "wait-for": "指定した時間待機",
"wait-loop": "指定した時間間隔毎に送信を繰り返す", "wait-loop": "指定した時間間隔毎に送信を繰り返す",
"for": "処理対象", "for": "処理対象",
"bytopics": "msg.topic毎", "bytopics": "毎",
"alltopics": "全メッセージ", "alltopics": "全メッセージ",
"duration": { "duration": {
"ms": "ミリ秒", "ms": "ミリ秒",
@ -719,6 +719,11 @@
"mac": "Mac (\\r)", "mac": "Mac (\\r)",
"windows": "Windows (\\r\\n)" "windows": "Windows (\\r\\n)"
}, },
"hdrout": {
"none": "カラムヘッダを送信しない",
"all": "カラムヘッダを常に送信する",
"once": "ヘッダを一度だけ送信する(msg.resetの受け付けると再送)"
},
"errors": { "errors": {
"csv_js": "本ードが処理できる形式は、CSV文字列またはJSONのみです", "csv_js": "本ードが処理できる形式は、CSV文字列またはJSONのみです",
"obj_csv": "オブジェクトをCSVへ変換する際の列名が設定されていません" "obj_csv": "オブジェクトをCSVへ変換する際の列名が設定されていません"

View File

@ -19,10 +19,10 @@
"body-parser": "1.19.0", "body-parser": "1.19.0",
"cheerio": "0.22.0", "cheerio": "0.22.0",
"content-type": "1.0.4", "content-type": "1.0.4",
"cookie-parser": "1.4.4", "cookie-parser": "1.4.5",
"cookie": "0.4.0", "cookie": "0.4.0",
"cors": "2.8.5", "cors": "2.8.5",
"cron": "1.8.2", "cron": "1.7.2",
"denque": "1.4.1", "denque": "1.4.1",
"fs-extra": "8.1.0", "fs-extra": "8.1.0",
"fs.notify": "0.0.4", "fs.notify": "0.0.4",
@ -33,7 +33,7 @@
"media-typer": "1.1.0", "media-typer": "1.1.0",
"mqtt": "2.18.8", "mqtt": "2.18.8",
"multer": "1.4.2", "multer": "1.4.2",
"mustache": "4.0.0", "mustache": "4.0.1",
"on-headers": "1.0.2", "on-headers": "1.0.2",
"raw-body": "2.4.1", "raw-body": "2.4.1",
"request": "2.88.0", "request": "2.88.0",

View File

@ -18,7 +18,7 @@
"dependencies": { "dependencies": {
"@node-red/util": "1.1.0", "@node-red/util": "1.1.0",
"semver": "6.3.0", "semver": "6.3.0",
"uglify-js": "3.8.0", "uglify-js": "3.8.1",
"when": "3.7.8" "when": "3.7.8"
} }
} }

View File

@ -203,10 +203,10 @@ LocalFileSystem.prototype.open = function(){
var newContext = self.cache._export(); var newContext = self.cache._export();
scopes.forEach(function(scope) { scopes.forEach(function(scope) {
var storagePath = getStoragePath(self.storageBaseDir,scope); var storagePath = getStoragePath(self.storageBaseDir,scope);
var context = newContext[scope]; var context = newContext[scope] || {};
var stringifiedContext = stringify(context); var stringifiedContext = stringify(context);
if (stringifiedContext.circular && !self.knownCircularRefs[scope]) { if (stringifiedContext.circular && !self.knownCircularRefs[scope]) {
log.warn(log._("error-circular",{scope:scope})); log.warn(log._("context.localfilesystem.error-circular",{scope:scope}));
self.knownCircularRefs[scope] = true; self.knownCircularRefs[scope] = true;
} else { } else {
delete self.knownCircularRefs[scope]; delete self.knownCircularRefs[scope];
@ -324,7 +324,7 @@ LocalFileSystem.prototype.set = function(scope, key, value, callback) {
} }
var stringifiedContext = stringify(obj); var stringifiedContext = stringify(obj);
if (stringifiedContext.circular && !self.knownCircularRefs[scope]) { if (stringifiedContext.circular && !self.knownCircularRefs[scope]) {
log.warn(log._("error-circular",{scope:scope})); log.warn(log._("context.localfilesystem.error-circular",{scope:scope}));
self.knownCircularRefs[scope] = true; self.knownCircularRefs[scope] = true;
} else { } else {
delete self.knownCircularRefs[scope]; delete self.knownCircularRefs[scope];

View File

@ -553,7 +553,7 @@ function getFlow(id) {
if (flow.label) { if (flow.label) {
result.label = flow.label; result.label = flow.label;
} }
if (flow.disabled) { if (flow.hasOwnProperty('disabled')) {
result.disabled = flow.disabled; result.disabled = flow.disabled;
} }
if (flow.hasOwnProperty('info')) { if (flow.hasOwnProperty('info')) {

View File

@ -18,7 +18,7 @@
"clone": "2.1.2", "clone": "2.1.2",
"i18next": "15.1.2", "i18next": "15.1.2",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.8.1", "jsonata": "1.8.3",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"when": "3.7.8" "when": "3.7.8"
} }

View File

@ -41,7 +41,7 @@
"fs-extra": "8.1.0", "fs-extra": "8.1.0",
"node-red-node-rbe": "^0.2.6", "node-red-node-rbe": "^0.2.6",
"node-red-node-tail": "^0.1.0", "node-red-node-tail": "^0.1.0",
"nopt": "4.0.1", "nopt": "4.0.3",
"semver": "6.3.0" "semver": "6.3.0"
}, },
"optionalDependencies": { "optionalDependencies": {

View File

@ -102,20 +102,20 @@ describe('trigger node', function() {
function basicTest(type, val, rval) { function basicTest(type, val, rval) {
it('should output 1st value when triggered ('+type+')', function(done) { it('should output 1st value when triggered ('+type+')', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:val, op1type:type, op2:"", op2type:"null", duration:"20", wires:[["n2"]] }, var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:val, op1type:type, op2:"", op2type:"null", duration:"20", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
process.env[val] = rval; process.env[val] = rval;
helper.load(triggerNode, flow, function() { helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
if (rval) { if (rval) {
msg.should.have.property("payload"); msg.should.have.property("payload");
should.deepEqual(msg.payload, rval); should.deepEqual(msg.payload, rval);
} }
else { else {
msg.should.have.property("payload", val); msg.should.have.property("payload", val);
} }
delete process.env[val]; delete process.env[val];
done(); done();
} }
@ -127,7 +127,7 @@ describe('trigger node', function() {
it('should output 2st value when triggered ('+type+')', function(done) { it('should output 2st value when triggered ('+type+')', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"foo", op1type:"str", op2:val, op2type:type, duration:"20", wires:[["n2"]] }, var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"foo", op1type:"str", op2:val, op2type:type, duration:"20", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
process.env[val] = rval; process.env[val] = rval;
helper.load(triggerNode, flow, function() { helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -136,17 +136,17 @@ describe('trigger node', function() {
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
if (c === 0) { if (c === 0) {
msg.should.have.property("payload", "foo"); msg.should.have.property("payload", "foo");
c++; c++;
} }
else { else {
if (rval) { if (rval) {
msg.should.have.property("payload"); msg.should.have.property("payload");
should.deepEqual(msg.payload, rval); should.deepEqual(msg.payload, rval);
} }
else { else {
msg.should.have.property("payload", val); msg.should.have.property("payload", val);
} }
delete process.env[val]; delete process.env[val];
done(); done();
} }
@ -378,6 +378,51 @@ describe('trigger node', function() {
}); });
}); });
it('should handle multiple other properties individually if asked to do so', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"topic", topic:"foo", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
c += 1;
if (c === 1) {
msg.should.have.a.property("payload", 1);
msg.should.have.a.property("foo", "A");
}
else if (c === 2) {
msg.should.have.a.property("payload", 1);
msg.should.have.a.property("foo", "B");
}
else if (c === 3) {
msg.should.have.a.property("payload", 1);
msg.should.have.a.property("foo", "C");
}
else if (c === 4) {
msg.should.have.a.property("payload", 0);
msg.should.have.a.property("foo", "A");
}
else if (c === 5) {
msg.should.have.a.property("payload", 0);
msg.should.have.a.property("foo", "B");
}
else if (c === 6) {
msg.should.have.a.property("payload", 0);
msg.should.have.a.property("foo", "C");
done();
}
} catch(err) {
done(err);
}
});
n1.emit("input", {payload:1,foo:"A"});
n1.emit("input", {payload:2,foo:"B"});
n1.emit("input", {payload:3,foo:"C"});
});
});
it('should be able to return things from flow and global context variables', function(done) { it('should be able to return things from flow and global context variables', function(done) {
var spy = sinon.stub(RED.util, 'evaluateNodeProperty', var spy = sinon.stub(RED.util, 'evaluateNodeProperty',
function(arg1, arg2, arg3, arg4, arg5) { if (arg5) { arg5(null, arg1) } else { return arg1; } } function(arg1, arg2, arg3, arg4, arg5) { if (arg5) { arg5(null, arg1) } else { return arg1; } }
@ -408,8 +453,8 @@ describe('trigger node', function() {
it('should be able to return things from persistable flow and global context variables', function (done) { it('should be able to return things from persistable flow and global context variables', function (done) {
var flow = [{"id": "n1", "type": "trigger", "name": "triggerNode", "op1": "#:(memory1)::foo", "op1type": "flow", var flow = [{"id": "n1", "type": "trigger", "name": "triggerNode", "op1": "#:(memory1)::foo", "op1type": "flow",
"op2": "#:(memory1)::bar", "op2type": "global", "duration": "20", "wires": [["n2"]], "z": "flow" }, "op2": "#:(memory1)::bar", "op2type": "global", "duration": "20", "wires": [["n2"]], "z": "flow" },
{"id": "n2", "type": "helper"}]; {"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () { helper.load(triggerNode, flow, function () {
initContext(function () { initContext(function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -442,11 +487,11 @@ describe('trigger node', function() {
it('should be able to return things from multiple persistable global context variables', function (done) { it('should be able to return things from multiple persistable global context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger", var flow = [{"id": "n1", "z": "flow", "type": "trigger",
"duration": "20", "wires": [["n2"]], "duration": "20", "wires": [["n2"]],
"op1": "#:(memory1)::val", "op1type": "global", "op1": "#:(memory1)::val", "op1type": "global",
"op2": "#:(memory2)::val", "op2type": "global" "op2": "#:(memory2)::val", "op2type": "global"
}, },
{"id": "n2", "type": "helper"}]; {"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () { helper.load(triggerNode, flow, function () {
initContext(function () { initContext(function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -481,11 +526,11 @@ describe('trigger node', function() {
it('should be able to return things from multiple persistable flow context variables', function (done) { it('should be able to return things from multiple persistable flow context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger", var flow = [{"id": "n1", "z": "flow", "type": "trigger",
"duration": "20", "wires": [["n2"]], "duration": "20", "wires": [["n2"]],
"op1": "#:(memory1)::val", "op1type": "flow", "op1": "#:(memory1)::val", "op1type": "flow",
"op2": "#:(memory2)::val", "op2type": "flow" "op2": "#:(memory2)::val", "op2type": "flow"
}, },
{"id": "n2", "type": "helper"}]; {"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () { helper.load(triggerNode, flow, function () {
initContext(function () { initContext(function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -520,11 +565,11 @@ describe('trigger node', function() {
it('should be able to return things from multiple persistable flow & global context variables', function (done) { it('should be able to return things from multiple persistable flow & global context variables', function (done) {
var flow = [{"id": "n1", "z": "flow", "type": "trigger", var flow = [{"id": "n1", "z": "flow", "type": "trigger",
"duration": "20", "wires": [["n2"]], "duration": "20", "wires": [["n2"]],
"op1": "#:(memory1)::val", "op1type": "flow", "op1": "#:(memory1)::val", "op1type": "flow",
"op2": "#:(memory2)::val", "op2type": "global" "op2": "#:(memory2)::val", "op2type": "global"
}, },
{"id": "n2", "type": "helper"}]; {"id": "n2", "type": "helper"}];
helper.load(triggerNode, flow, function () { helper.load(triggerNode, flow, function () {
initContext(function () { initContext(function () {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -818,6 +863,40 @@ describe('trigger node', function() {
}); });
}); });
it('should be able to send 2nd message to a 2nd output', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"hello", op2:"world", duration:"50", outputs:2, wires:[["n2"],["n3"]] },
{id:"n2", type:"helper"}, {id:"n3", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var n3 = helper.getNode("n3");
var c = 0;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.a.property("payload", "hello");
msg.should.have.a.property("topic", "test");
c+=1;
}
else { done(err); }
}
catch(err) { done(err); }
});
n3.on("input", function(msg) {
try {
if (c === 1) {
msg.should.have.a.property("payload", "world");
msg.should.have.a.property("topic", "test");
done();
}
else { done(err); }
}
catch(err) { done(err); }
});
n1.emit("input", {payload:"go",topic:"test"});
});
});
it('should handle string null as null', function(done) { it('should handle string null as null', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"pay", op1:"null", op2:"null", duration:"40", wires:[["n2"]] }, var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"pay", op1:"null", op2:"null", duration:"40", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];

View File

@ -1,3 +1,4 @@
/* eslint-disable no-undef */
/** /**
* Copyright JS Foundation and other contributors, http://js.foundation * Copyright JS Foundation and other contributors, http://js.foundation
* *
@ -70,12 +71,13 @@ describe('CSV node', function() {
it('should convert a simple csv string to a javascript object', function(done) { it('should convert a simple csv string to a javascript object', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
msg.should.have.property('payload', { a: 1, b: 2, c: 3, d: 4 }); msg.should.have.property('payload', { a: 1, b: 2, c: 3, d: 4 });
msg.should.have.property('columns', "a,b,c,d");
check_parts(msg, 0, 1); check_parts(msg, 0, 1);
done(); done();
}); });
@ -86,7 +88,7 @@ describe('CSV node', function() {
it('should remove quotes and whitespace from template', function(done) { it('should remove quotes and whitespace from template', function(done) {
var flow = [ { id:"n1", type:"csv", temp:'"a", "b" , " c "," d " ', wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:'"a", "b" , " c "," d " ', wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -102,12 +104,13 @@ describe('CSV node', function() {
it('should create column names if no template provided', function(done) { it('should create column names if no template provided', function(done) {
var flow = [ { id:"n1", type:"csv", temp:'', wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:'', wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
msg.should.have.property('payload', { col1: 1, col2: 2, col3: 3, col4: 4 }); msg.should.have.property('payload', { col1: 1, col2: 2, col3: 3, col4: 4 });
msg.should.have.property('columns', "col1,col2,col3,col4");
check_parts(msg, 0, 1); check_parts(msg, 0, 1);
done(); done();
}); });
@ -118,12 +121,13 @@ describe('CSV node', function() {
it('should allow dropping of fields from the template', function(done) { it('should allow dropping of fields from the template', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,,,d", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,,,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
msg.should.have.property('payload', { a: 1, d: 4 }); msg.should.have.property('payload', { a: 1, d: 4 });
msg.should.have.property('columns', 'a,d');
check_parts(msg, 0, 1); check_parts(msg, 0, 1);
done(); done();
}); });
@ -134,7 +138,7 @@ describe('CSV node', function() {
it('should leave numbers starting with 0, e and + as strings (except 0.)', function(done) { it('should leave numbers starting with 0, e and + as strings (except 0.)', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -150,7 +154,7 @@ describe('CSV node', function() {
it('should not parse numbers when told not to do so', function(done) { it('should not parse numbers when told not to do so', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", strings:false, wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", strings:false, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -166,7 +170,7 @@ describe('CSV node', function() {
it('should leave handle strings with scientific notation as numbers', function(done) { it('should leave handle strings with scientific notation as numbers', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -183,7 +187,7 @@ describe('CSV node', function() {
it('should allow quotes in the input (but drop blank strings)', function(done) { it('should allow quotes in the input (but drop blank strings)', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g,h", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g,h", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -327,12 +331,13 @@ describe('CSV node', function() {
it('should be able to output multiple lines as one array', function(done) { it('should be able to output multiple lines as one array', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", multi:"yes", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", multi:"yes", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
msg.should.have.property('payload', [ { a: 1, b: 2, c: 3, d: 4 },{ a: 5, b: -6, c: '07', d: '+8' },{ a: 9, b: 0, c: 'a', d: 'b' },{ a: 'c', b: 'd', c: 'e', d: 'f' } ]); msg.should.have.property('payload', [ { a: 1, b: 2, c: 3, d: 4 },{ a: 5, b: -6, c: '07', d: '+8' },{ a: 9, b: 0, c: 'a', d: 'b' },{ a: 'c', b: 'd', c: 'e', d: 'f' } ]);
msg.should.have.property('columns','a,b,c,d');
msg.should.not.have.property('parts'); msg.should.not.have.property('parts');
done(); done();
}); });
@ -343,7 +348,7 @@ describe('CSV node', function() {
it('should handle numbers in strings but not IP addresses', function(done) { it('should handle numbers in strings but not IP addresses', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -359,7 +364,7 @@ describe('CSV node', function() {
it('should preserve parts property', function(done) { it('should preserve parts property', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -403,7 +408,7 @@ describe('CSV node', function() {
it('should skip several lines from start if requested', function(done) { it('should skip several lines from start if requested', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", skip: 2, wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", skip: 2, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -417,9 +422,9 @@ describe('CSV node', function() {
}); });
}); });
it('should skip several lines from start then use next line as a tempate', function(done) { it('should skip several lines from start then use next line as a template', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrin:true, skip: 2, wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrin:true, skip: 2, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -435,7 +440,7 @@ describe('CSV node', function() {
it('should skip several lines from start and correct parts', function(done) { it('should skip several lines from start and correct parts', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", skip: 2, wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", skip: 2, wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -467,11 +472,13 @@ describe('CSV node', function() {
n2.on("input", function(msg) { n2.on("input", function(msg) {
if (c === 0) { if (c === 0) {
msg.should.have.property('payload', { w: 1, x: 2, y: 3, z: 4 }); msg.should.have.property('payload', { w: 1, x: 2, y: 3, z: 4 });
msg.should.have.property('columns', 'w,x,y,z');
check_parts(msg, 0, 2); check_parts(msg, 0, 2);
c += 1; c += 1;
} }
else { else {
msg.should.have.property('payload', { w: 5, x: 6, y: 7, z: 8 }); msg.should.have.property('payload', { w: 5, x: 6, y: 7, z: 8 });
msg.should.have.property('columns', 'w,x,y,z');
check_parts(msg, 1, 2); check_parts(msg, 1, 2);
done(); done();
} }
@ -495,7 +502,7 @@ describe('CSV node', function() {
it('should convert a simple object back to a csv', function(done) { it('should convert a simple object back to a csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -513,7 +520,7 @@ describe('CSV node', function() {
it('should convert a simple object back to a csv with no template', function(done) { it('should convert a simple object back to a csv with no template', function(done) {
var flow = [ { id:"n1", type:"csv", temp:" ", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:" ", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -531,7 +538,7 @@ describe('CSV node', function() {
it('should handle a template with spaces in the property names', function(done) { it('should handle a template with spaces in the property names', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b o,c p,,e", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b o,c p,,e", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -549,7 +556,7 @@ describe('CSV node', function() {
it('should convert an array of objects to a multi-line csv', function(done) { it('should convert an array of objects to a multi-line csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -567,7 +574,7 @@ describe('CSV node', function() {
it('should convert a simple array back to a csv', function(done) { it('should convert a simple array back to a csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -585,7 +592,7 @@ describe('CSV node', function() {
it('should convert an array of arrays back to a multi-line csv', function(done) { it('should convert an array of arrays back to a multi-line csv', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -603,7 +610,7 @@ describe('CSV node', function() {
it('should be able to include column names as first row', function(done) { it('should be able to include column names as first row', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrout:true, ret:"\r\n", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrout:true, ret:"\r\n", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -619,9 +626,36 @@ describe('CSV node', function() {
}); });
}); });
it('should be able to pass in column names', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"", hdrout:"once", ret:"\r\n", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var count = 0;
n2.on("input", function(msg) {
count += 1;
try {
if (count === 1) {
msg.should.have.property('payload', 'a,,b,a\r\n4,,3,4\r\n');
}
if (count === 3) {
msg.should.have.property('payload', '4,,3,4\r\n');
done()
}
}
catch(e) { done(e); }
});
var testJson = [{ d: 1, b: 3, c: 2, a: 4 }];
n1.emit("input", {payload:testJson, columns:"a,,b,a"});
n1.emit("input", {payload:testJson});
n1.emit("input", {payload:testJson});
});
});
it('should handle quotes and sub-properties', function(done) { it('should handle quotes and sub-properties', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -641,7 +675,7 @@ describe('CSV node', function() {
it('should just pass through if no payload provided', function(done) { it('should just pass through if no payload provided', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
@ -661,7 +695,7 @@ describe('CSV node', function() {
it('should warn if provided a number or boolean', function(done) { it('should warn if provided a number or boolean', function(done) {
var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(csvNode, flow, function() { helper.load(csvNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");