1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Update Join node runtime to match UI changes

This commit is contained in:
Nick O'Leary 2016-06-05 23:32:03 +01:00
parent 9f8c32ce8f
commit e594ffe0f8
4 changed files with 169 additions and 144 deletions

View File

@ -124,21 +124,11 @@
this.options.types = this.options.types||Object.keys(allOptions);
var hasSubOptions = false;
this.typeMap = {};
this.types = this.options.types.map(function(opt) {
var result;
if (typeof opt === 'string') {
result = allOptions[opt];
} else {
result = opt;
}
that.typeMap[result.value] = result;
if (result.options) {
hasSubOptions = true;
}
return result;
});
this.selectTrigger = $('<a href="#"></a>').prependTo(this.uiSelect);
$('<i class="fa fa-sort-desc"></i>').appendTo(this.selectTrigger);
this.selectLabel = $('<span></span>').appendTo(this.selectTrigger);
this.types(this.options.types);
if (this.options.typeField) {
this.typeField = $(this.options.typeField).hide();
@ -150,13 +140,6 @@
this.typeField = $("<input>",{type:'hidden'}).appendTo(this.uiSelect);
}
this.selectTrigger = $('<a href="#"></a>').prependTo(this.uiSelect);
$('<i class="fa fa-sort-desc"></i>').appendTo(this.selectTrigger);
if (this.types.length === 1) {
this.selectTrigger.addClass("disabled");
}
this.selectLabel = $('<span></span>').appendTo(this.selectTrigger);
this.element.on('focus', function() {
that.uiSelect.addClass('red-ui-typedInput-focus');
});
@ -166,36 +149,29 @@
this.element.on('change', function() {
that.validate();
})
if (this.types.length > 1) {
this.selectTrigger.click(function(event) {
event.preventDefault();
this.selectTrigger.click(function(event) {
event.preventDefault();
if (that.typeList.length > 1) {
that._showMenu(that.menu,that.selectTrigger);
});
} else {
this.selectTrigger.click(function(event) {
event.preventDefault();
} else {
that.element.focus();
})
}
}
});
// explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
this.optionSelectTrigger = $('<a href="#" class="red-ui-typedInput-option-trigger" style="display:inline-block"><i class="fa fa-sort-desc"></i></a>').appendTo(this.uiSelect);
this.optionSelectLabel = $('<span></span>').prependTo(this.optionSelectTrigger);
this.optionSelectTrigger.click(function(event) {
event.preventDefault();
if (that.optionMenu) {
that.optionMenu.css({
minWidth:that.optionSelectLabel.width()
});
if (hasSubOptions) {
// explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
this.optionSelectTrigger = $('<a href="#" class="red-ui-typedInput-option-trigger" style="display:inline-block"><i class="fa fa-sort-desc"></i></a>').appendTo(this.uiSelect);
this.optionSelectLabel = $('<span></span>').prependTo(this.optionSelectTrigger);
this.optionSelectTrigger.click(function(event) {
event.preventDefault();
if (that.optionMenu) {
that.optionMenu.css({
minWidth:that.optionSelectLabel.width()
});
that._showMenu(that.optionMenu,that.optionSelectLabel)
}
});
}
this.menu = this._createMenu(this.types, function(v) { that.type(v) });
this.type(this.options.default||this.types[0].value);
that._showMenu(that.optionMenu,that.optionSelectLabel)
}
});
this.type(this.options.default||this.typeList[0].value);
},
_hideMenu: function(menu) {
$(document).off("mousedown.close-property-select");
@ -278,7 +254,6 @@
return labelWidth;
},
_resize: function() {
if (this.typeMap[this.propertyType] && this.typeMap[this.propertyType].hasValue === false) {
this.selectTrigger.width(this.uiWidth+5);
} else {
@ -298,6 +273,29 @@
_destroy: function() {
this.menu.remove();
},
types: function(types) {
var that = this;
var currentType = this.type();
this.typeMap = {};
this.typeList = types.map(function(opt) {
var result;
if (typeof opt === 'string') {
result = allOptions[opt];
} else {
result = opt;
}
that.typeMap[result.value] = result;
return result;
});
this.selectTrigger.toggleClass("disabled", this.typeList.length === 1);
if (this.menu) {
this.menu.remove();
}
this.menu = this._createMenu(this.typeList, function(v) { that.type(v) });
if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
this.type(this.typeList[0].value);
}
},
width: function(desiredWidth) {
this.uiWidth = desiredWidth;
this._resize();
@ -359,9 +357,14 @@
this.optionSelectTrigger.hide();
}
if (opt.hasValue === false) {
this.oldValue = this.element.val();
this.element.val("");
this.element.hide();
} else {
if (this.oldValue !== undefined) {
this.element.val(this.oldValue);
delete this.oldValue;
}
this.element.show();
}
this.element.trigger('change');

View File

@ -44,7 +44,7 @@
color:"#E2D96E",
defaults: {
name: {value:""},
splt: {value:""}
splt: {value:"\\n"}
},
inputs:1,
outputs:1,
@ -61,12 +61,12 @@
<script type="text/x-red" data-template-name="join">
<div class="form-row">
<label for="node-input-out"><i class="fa fa-long-arrow-right"></i> Output</label>
<select id="node-input-out" style="width:70%; margin-right:5px;">
<label for="node-input-build"><i class="fa fa-long-arrow-right"></i> Output</label>
<select id="node-input-build" style="width:70%; margin-right:5px;">
<option value="auto">automatic</option>
<option value="str">a String of joined properties</option>
<option value="arr">an Array of properties</option>
<option value="obj">an Object of key/property pairs</option>
<option value="string">a String of joined properties</option>
<option value="array">an Array of properties</option>
<option value="object">an Object of key/property pairs</option>
</select>
</div>
<div class="form-row node-row-key">
@ -78,22 +78,24 @@
<input type="text" id="node-input-property">
<input type="hidden" id="node-input-propertyType">
</div>
<div class="form-row node-row-join">
<label for="node-input-join" style="padding-right: 10px; box-sizing: border-box; text-align: right;">joined using</label>
<input type="text" id="node-input-join" style="width: 40px">
<div class="form-row node-row-joiner">
<label for="node-input-joiner" style="padding-right: 10px; box-sizing: border-box; text-align: right;">joined using</label>
<input type="text" id="node-input-joiner" style="width: 40px">
</div>
<div class="form-row node-row-trigger">
<label style="width: auto;">Send the message:</label>
<div style="height: 40px;">
<label style="margin-left: 40px; width: auto;"><input type="checkbox" style="width: auto;margin-bottom: 5px;"> after a fixed number of messages: <input type="text" style="width: 75px;"></label>
</div>
<div style="height: 40px;">
<label style="margin-left: 40px; width: auto;"><input type="checkbox" style="width: auto;margin-bottom: 5px;"> after a timeout following the first message: <input placeholder="seconds" type="text" style="width: 75px;"></label>
</div>
<div style="height: 40px; padding-top: 6px;" class="node-row-accumulate">
<label style="margin-left: 40px; width: auto;"><input type="checkbox" style="width: auto;margin-bottom: 5px;"> whenever a property is updated</label>
</div>
<ul>
<li style="height: 40px;">
<label style="width: 280px;" for="node-input-count">After a fixed number of messages:</label> <input id="node-input-count" placeholder="count" type="text" style="width: 75px;">
</li>
<li style="height: 40px;">
<label style="width: 280px;" for="node-input-timeout">After a timeout following the first message:</label> <input id="node-input-timeout" placeholder="seconds" type="text" style="width: 75px;">
</li>
<li style="height: 40px; padding-top: 6px;" class="node-row-accumulate">
<label style="width: auto;"><input type="checkbox" id="node-input-accumulate" style="width: auto;margin-bottom: 5px;margin-right: 5px;"> whenever a property is updated</label>
</li>
</ul>
</div>
<div class="form-tips form-tips-auto hide">This mode assumes this node is either
@ -122,13 +124,14 @@
color:"#E2D96E",
defaults: {
name: {value:""},
out: { value:"auto"},
build: { value:"auto"},
property: { value: "payload"},
propertyType: { value:"msg"},
key: {value:"topic"},
joiner: { value:"\\n"},
timeout: {value:""},
timerr: {value:"send"},
count: {value:""},
joiner: {value:""},
build: {value:"array"}
accumulate: {value: false}
},
inputs:1,
outputs:1,
@ -140,14 +143,19 @@
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$("#node-input-out").change(function(e) {
$("#node-input-build").change(function(e) {
var val = $(this).val();
$(".node-row-key").toggle(val==='obj');
$(".node-row-property").toggle(val!=='auto');
$(".node-row-join").toggle(val==='str');
$(".node-row-joiner").toggle(val==='str');
$(".node-row-trigger").toggle(val!=='auto');
$(".node-row-accumulate").toggle(val==='obj');
$(".form-tips-auto").toggle(val==='auto');
if (val === 'str') {
$("#node-input-property").typedInput('types',['msg']);
} else if (val !== 'auto') {
$("#node-input-property").typedInput('types',['msg', {value:"full",label:"complete message",hasValue:false}]);
}
})
$("#node-input-property").typedInput({
@ -158,7 +166,7 @@
types:['msg']
})
$("#node-input-out").change();
$("#node-input-build").change();
}
});
</script>

View File

@ -68,6 +68,9 @@ module.exports = function(RED) {
function JoinNode(n) {
RED.nodes.createNode(this,n);
this.property = n.property||"payload";
this.propertyType = n.propertyType||"msg";
this.key = n.key||"topic";
this.timer = Number(n.timeout || 0);
this.timerr = n.timerr || "send";
this.count = Number(n.count || 0);
@ -78,10 +81,6 @@ module.exports = function(RED) {
var misc = (this.build === "array") ? [] : {};
var tout;
function isObject (item) {
return (typeof item === "object" && !Array.isArray(item)&& ! Buffer.isBuffer(item) && item !== null);
}
// if array came from a string then reassemble it and send it
var sendIt = function(m) {
if (inflight[m.parts.id].ch !== undefined) { // if it was a string - rejoin it using the split char
@ -116,80 +115,93 @@ module.exports = function(RED) {
m.payload = misc.join(node.joiner.replace(/\\n/,"\n").replace(/\\r/,"\r").replace(/\\t/,"\t").replace(/\\e/,"\e").replace(/\\f/,"\c").replace(/\\0/,"\0"));
}
if (node.build === "array") { misc = []; }
if (node.build === "object") { misc = {}; }
else if (node.build === "object") { misc = {}; }
node.send(m);
}
this.on("input", function(msg) {
if (msg.hasOwnProperty("payload")) {
if (msg.hasOwnProperty("parts")) { // only act if it has parts
var count = node.count || msg.parts.count || 1;
if (msg.parts.hasOwnProperty("index")) { // it's a numbered part (from a split node)
if (!inflight[msg.parts.id]) { // New message - create new empty array of correct size
if (msg.parts.type === "object") {
inflight[msg.parts.id] = {i:0, a:{}, c:msg.parts.count, ch:msg.parts.ch, t:msg.parts.type};
} else { // it's an array or string
inflight[msg.parts.id] = {i:0, a:new Array(msg.parts.count), ch:msg.parts.ch, t:msg.parts.type};
}
if (node.timer !== 0) { // If there is a timer to set start it now
inflight[msg.parts.id].timeout = setTimeout(function() {
if (node.timerr === "send") { sendIt(msg); }
if (node.timerr === "error") { node.error("Incomplete",msg); }
delete inflight[msg.parts.id];
}, node.timer);
}
}
var property;
if (node.propertyType == "full") {
property = msg;
} else {
try {
property = RED.util.getMessageProperty(msg,node.property);
} catch(err) {
node.warn("Message property "+node.property+" not found");
return;
}
}
if (msg.hasOwnProperty("parts")) {
// only act if it has parts
var count = node.count || msg.parts.count || 1;
if (msg.parts.hasOwnProperty("index")) {
// it's a numbered part (from a split node)
if (!inflight[msg.parts.id]) {
// New message - create new empty array of correct size
if (msg.parts.type === "object") {
inflight[msg.parts.id].a[msg.parts.key] = msg.payload; // Add to the tracking array
inflight[msg.parts.id].i = Object.keys(inflight[msg.parts.id].a).length;
} else { // it's an array or string
inflight[msg.parts.id].a[msg.parts.index] = msg.payload; // Add to the tracking array
inflight[msg.parts.id].i += 1; // Increment the count
inflight[msg.parts.id] = {i:0, a:{}, c:msg.parts.count, ch:msg.parts.ch, t:msg.parts.type};
} else {
// it's an array or string
inflight[msg.parts.id] = {i:0, a:new Array(msg.parts.count), ch:msg.parts.ch, t:msg.parts.type};
}
if (inflight[msg.parts.id].i >= count) { sendIt(msg); } // All arrived - send
} // otherwise ignore it
if (msg.hasOwnProperty("complete")) { // if set then send right away anyway...
delete(msg.complete);
if (node.timer !== 0) { // If there is a timer to set start it now
inflight[msg.parts.id].timeout = setTimeout(function() {
if (node.timerr === "send") { sendIt(msg); }
else if (node.timerr === "error") { node.error("Incomplete",msg); }
delete inflight[msg.parts.id];
}, node.timer);
}
}
if (msg.parts.type === "object") {
// Add to the tracking array
inflight[msg.parts.id].a[msg.parts.key] = property;
inflight[msg.parts.id].i = Object.keys(inflight[msg.parts.id].a).length;
} else {
// it's an array or string
// Add to the tracking array
inflight[msg.parts.id].a[msg.parts.index] = property;
// Increment the count
inflight[msg.parts.id].i += 1;
}
if (inflight[msg.parts.id].i >= count) {
// All arrived - send
sendIt(msg);
}
} // otherwise ignore it
if (msg.hasOwnProperty("complete")) { // if set then send right away anyway...
delete(msg.complete);
sendIt(msg);
}
} else {
// The case for any messages arriving without parts - ie random messages you want to join.
else {
var l;
if (node.build === "array") { // simple case of build the array
misc.push(msg.payload); // Add the payload to an array
l = misc.length;
} else { // OK so let's build an object
if ((msg.key === undefined) && ((msg.topic === undefined) || (msg.topic === ''))) {
if (isObject(msg.payload)) { // if it's already an object (and no topic or key) just append it
misc = Object.assign(misc,msg.payload);
l = Object.keys(misc).length;
}
else { // if no topic or key and not an object then warn and drop it.
node.warn("key or topic not defined");
return;
}
}
else { // if it's got a msg.key or msg.topic then use key||topic as the property name
misc[ msg.key || msg.topic ] = msg.payload;
//if (msg.topic) { msg.topic = (msg.topic.split('/')).slice(0,-1).join('/'); }
l = Object.keys(misc).length;
}
}
if (l >= node.count) { sendMisc(msg); } // if it's long enough send it
else if (msg.hasOwnProperty("complete")) { // if set then send right away anyway...
delete(msg.complete);
sendMisc(msg);
}
else if ((node.timer !== 0) && !tout) { // if not start the timer if there is one.
tout = setTimeout(function() {
if (node.timerr === "send") { sendMisc(msg); }
if (node.timerr === "error") { node.error("Timeout",msg); }
if (node.build === "array") { misc = []; }
if (node.build === "object") { misc = {}; }
}, node.timer);
var l;
if (node.build === "array" || node.build === "string") {
// simple case of build the array
// Add the payload to an array
misc.push(property);
l = misc.length;
} else {
// OK so let's build an object
if (msg.hasOwnProperty(node.key) && msg[node.key] !== "") {
misc[msg[node.key]] = property;
}
l = Object.keys(misc).length;
}
if (l >= node.count) {
// if it's long enough send it
sendMisc(msg);
} else if (msg.hasOwnProperty("complete")) {
// if set then send right away anyway...
delete(msg.complete);
sendMisc(msg);
} else if ((node.timer !== 0) && !tout) {
// if not start the timer if there is one.
tout = setTimeout(function() {
if (node.timerr === "send") { sendMisc(msg); }
else if (node.timerr === "error") { node.error("Timeout",msg); }
if (node.build === "array") { misc = []; }
else if (node.build === "object") { misc = {}; }
}, node.timer);
}
}
});

View File

@ -181,8 +181,9 @@ describe('JOIN node', function() {
msg.payload.should.have.property("b","2");
msg.payload.should.have.property("c",true);
msg.payload.should.have.property("d");
msg.payload.d.should.have.property("e",5);
msg.payload.should.have.property("f",6);
msg.payload.d.should.have.property("e",7);
msg.payload.should.have.property("g");
msg.payload.g.should.have.property("f",6);
done();
}
catch(e) { }//console.log(e); }
@ -191,7 +192,8 @@ describe('JOIN node', function() {
n1.receive({payload:"2", topic:"b"});
n1.receive({payload:true, topic:"c"});
n1.receive({payload:{e:5}, topic:"d"});
n1.receive({payload:{f:6}});
n1.receive({payload:{e:7}, topic:"d"});
n1.receive({payload:{f:6}, topic:"g"});
});
});