1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00
node-red/nodes/core/logic/10-switch.html
Hiroyasu Nishiyama 6310de0d20 Initial support of sequence rules for SWITCH node (#1545)
* new UI for parts support of SWITCH node

* update UI of SWITCH node for parts support

* add server side code of new SWITCH node

* update info document of SWITCH node

* add tests for new SWITCH node features

* add test for too many pending messages & related fixes

* fix handling when msg is undefined

* tabs -> spaces

* fixed meaning of "repair sequence" in SWITCH node docs

* add a note on restricting internally kept messages

* change label and position in menu of "pos. between" rule

* fixed typos (again, sorry)
2018-01-17 10:08:58 +00:00

477 lines
23 KiB
HTML

<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="switch">
<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-row">
<label data-i18n="switch.label.property"></label>
<input type="text" id="node-input-property" style="width: 70%"/>
<input type="hidden" id="node-input-outputs"/>
</div>
<div class="form-row node-input-rule-container-row">
<ol id="node-input-rule-container"></ol>
</div>
<div class="form-row">
<select id="node-input-checkall" style="width:100%; margin-right:5px;">
<option value="true" data-i18n="switch.checkall"></option>
<option value="false" data-i18n="switch.stopfirst"></option>
</select>
</div>
<div class="form-row">
<input type="checkbox" id="node-input-repair" style="display: inline-block; width: auto; vertical-align: top;">
<label style="width: auto;" for="node-input-repair"><span data-i18n="switch.label.repair"></span></label></input>
</div>
</script>
<script type="text/x-red" data-help-name="switch">
<p>Route messages based on their property values.</p>
<h3>Details</h3>
<p>When a message arrives, the node will evaluate each of the defined rules
and forward the message to the corresponding outputs of any matching rules.</p>
<p>Optionally, the node can be set to stop evaluating rules once it finds one
that matches.</p>
<p>The rules can be evaluated against an individual message property, a flow or global
context property or the result of a JSONata expression.</p>
<h3>Rules</h3>
<p>Routing rules are categorized into three:</p>
<dl>
<dt>value rules</dt>
<dd>
<table>
<tr>
<th>operator</th>
<th>description</th>
</tr>
<tr>
<td><b>==</b></td>
<td>property value is equals to specified value</td>
</tr>
<tr>
<td><b>!=</b></td>
<td>property value is not equals to specified value</td>
</tr>
<tr>
<td><b>&lt;</b></td>
<td>property value is less than specified value</td>
</tr>
<tr>
<td><b>&lt;=</b></td>
<td>property value is less than or equals to specified value</td>
</tr>
<tr>
<td><b>&gt;</b></td>
<td>property value is greater than specified value</td>
</tr>
<tr>
<td><b>&gt;=</b></td>
<td>property value is greater than or equals to specified value</td>
</tr>
<tr>
<td><b>is between</b></td>
<td>property value is between specified values</td>
</tr>
<tr>
<td><b>contains</b></td>
<td>property string value contains specified string value</td>
</tr>
<tr>
<td><b>matches regex</b></td>
<td>property string value matches specified regex</td>
</tr>
<tr>
<td><b>is true</b></td>
<td>property value is true</td>
</tr>
<tr>
<td><b>is false</b></td>
<td>property value is false</td>
</tr>
<tr>
<td><b>is null</b></td>
<td>property value is null</td>
</tr>
<tr>
<td><b>is not null</b></td>
<td>property value is not null</td>
</tr>
</table>
</dd>
<dt>sequence rules</dt>
<dd>
<table>
<tr>
<th>operator</th>
<th>description</th>
</tr>
<tr>
<td><b>head</b></td>
<td>message is included in the first specified number of messages in a sequence</td>
</tr>
<tr>
<td><b>tail</b></td>
<td>message is included in the last specified number of messages in a sequence</td>
</tr>
<tr>
<td><b>pos. between</b></td>
<td>message is included between specified positions in a sequence</td>
</tr>
</table>
</dd>
<dt>other rules</dt>
<dd>
<table>
<tr>
<th>operator</th>
<th>description</th>
</tr>
<tr>
<td><b>JSONata exp</b></td>
<td>specified JSONata expression evaluate to true. In JSONata expression, $I represents <code>msg.parts.index</code> and $N represents <code>msg.parts.count</code> respectively if exists.</td>
</tr>
<tr>
<td><b>otherwise</b></td>
<td>no matching rule found</td>
</tr>
</table>
</dd>
</dl>
<h3>Repair Sequence</h3>
<p>If <b>repair sequence (reconstruct parts property of outgoing messages)</b> checkbox is selected, <code>msg.parts</code> property are reconstructed for each port to make forks of valid sequences. Otherwise, <code>msg.parts</code> property of incoming messages are passed through.</p>
<h3>Note:</h3>
<p>This node internally keeps messages for its operation if <b>repair sequence</b> checkbox is ON. In order to prevent unexpected memory usage, maximum number of messages kept can be specified by <code>switchMaxKeptMsgsCount</code> property in <b>settings.js</b>.</p>
</script>
<script type="text/javascript">
(function() {
var operators = [
{v:"eq",t:"==",kind:'V'},
{v:"neq",t:"!=",kind:'V'},
{v:"lt",t:"<",kind:'V'},
{v:"lte",t:"<=",kind:'V'},
{v:"gt",t:">",kind:'V'},
{v:"gte",t:">=",kind:'V'},
{v:"btwn",t:"switch.rules.btwn",kind:'V'},
{v:"cont",t:"switch.rules.cont",kind:'V'},
{v:"regex",t:"switch.rules.regex",kind:'V'},
{v:"true",t:"switch.rules.true",kind:'V'},
{v:"false",t:"switch.rules.false",kind:'V'},
{v:"null",t:"switch.rules.null",kind:'V'},
{v:"nnull",t:"switch.rules.nnull",kind:'V'},
{v:"head",t:"switch.rules.head",kind:'S'},
{v:"index",t:"switch.rules.index",kind:'S'},
{v:"tail",t:"switch.rules.tail",kind:'S'},
{v:"jsonata_exp",t:"switch.rules.exp",kind:'O'},
{v:"else",t:"switch.rules.else",kind:'O'}
];
function clipValueLength(v) {
if (v.length > 15) {
return v.substring(0,15)+"...";
}
return v;
}
function getValueLabel(t,v) {
if (t === 'str') {
return '"'+clipValueLength(v)+'"';
} else if (t === 'msg' || t==='flow' || t==='global') {
return t+"."+clipValueLength(v);
}
return clipValueLength(v);
}
RED.nodes.registerType('switch', {
color: "#E2D96E",
category: 'function',
defaults: {
name: {value:""},
property: {value:"payload", required:true, validate: RED.validators.typedInput("propertyType")},
propertyType: { value:"msg" },
rules: {value:[{t:"eq", v:"", vt:"str"}]},
checkall: {value:"true", required:true},
repair: {value:false},
outputs: {value:1}
},
inputs: 1,
outputs: 1,
outputLabels: function(index) {
var rule = this.rules[index];
var label = "";
if (rule) {
for (var i=0;i<operators.length;i++) {
if (operators[i].v === rule.t) {
label = /^switch/.test(operators[i].t)?this._(operators[i].t):operators[i].t;
break;
}
}
if ((rule.t === 'btwn') || (rule.t === 'index')) {
console.log(`; ${label}/${JSON.stringify(rule)}`);
label += " "+getValueLabel(rule.vt,rule.v)+" & "+getValueLabel(rule.v2t,rule.v2);
} else if (rule.t !== 'true' && rule.t !== 'false' && rule.t !== 'null' && rule.t !== 'nnull' && rule.t !== 'else' ) {
label += " "+getValueLabel(rule.vt,rule.v);
}
return label;
}
},
icon: "switch.png",
label: function() {
return this.name||"switch";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var node = this;
var previousValueType = {value:"prev",label:this._("inject.previous"),hasValue:false};
$("#node-input-property").typedInput({default:this.propertyType||'msg',types:['msg','flow','global','jsonata']});
var outputCount = $("#node-input-outputs").val("{}");
var andLabel = this._("switch.and");
var caseLabel = this._("switch.ignorecase");
function resizeRule(rule) {
var newWidth = rule.width();
var selectField = rule.find("select");
var type = selectField.val()||"";
var valueField = rule.find(".node-input-rule-value");
var numField = rule.find(".node-input-rule-num-value");
var expField = rule.find(".node-input-rule-exp-value");
var btwnField1 = rule.find(".node-input-rule-btwn-value");
var btwnField2 = rule.find(".node-input-rule-btwn-value2");
var selectWidth;
if (type.length < 4) {
selectWidth = 60;
} else if (type === "regex") {
selectWidth = 147;
} else {
selectWidth = 120;
}
selectField.width(selectWidth);
if ((type === "btwn") || (type === "index")) {
btwnField1.typedInput("width",(newWidth-selectWidth-70));
btwnField2.typedInput("width",(newWidth-selectWidth-70));
} else if ((type === "head") || (type === "tail")) {
numField.typedInput("width",(newWidth-selectWidth-70));
} else if (type === "jsonata_exp") {
expField.typedInput("width",(newWidth-selectWidth-70));
} else {
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
// valueField.hide();
} else {
valueField.typedInput("width",(newWidth-selectWidth-70));
}
}
}
$("#node-input-rule-container").css('min-height','250px').css('min-width','450px').editableList({
addItem: function(container,i,opt) {
if (!opt.hasOwnProperty('r')) {
opt.r = {};
}
var rule = opt.r;
if (!rule.hasOwnProperty('t')) {
rule.t = 'eq';
}
if (!opt.hasOwnProperty('i')) {
opt._i = Math.floor((0x99999-0x10000)*Math.random()).toString(16);
}
var row = $('<div/>').appendTo(container);
var row2 = $('<div/>',{style:"padding-top: 5px; padding-left: 175px;"}).appendTo(container);
var row3 = $('<div/>',{style:"padding-top: 5px; padding-left: 102px;"}).appendTo(container);
var selectField = $('<select/>',{style:"width:120px; margin-left: 5px; text-align: center;"}).appendTo(row);
var group0 = $('<optgroup/>', { label: "value rules" }).appendTo(selectField);
for (var d in operators) {
if(operators[d].kind === 'V') {
group0.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t));
}
}
var group1 = $('<optgroup/>', { label: "sequence rules" }).appendTo(selectField);
for (var d in operators) {
if(operators[d].kind === 'S') {
group1.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t));
}
}
for (var d in operators) {
if(operators[d].kind === 'O') {
selectField.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t));
}
}
var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num','jsonata',previousValueType]});
var numValueField = $('<input/>',{class:"node-input-rule-num-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['flow','global','num','jsonata']});
var expValueField = $('<input/>',{class:"node-input-rule-exp-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'jsonata',types:['jsonata']});
var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata',previousValueType]});
var btwnAndLabel = $('<span/>',{class:"node-input-rule-btwn-label"}).text(" "+andLabel+" ").appendTo(row3);
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"margin-left:2px;"}).appendTo(row3).typedInput({default:'num',types:['msg','flow','global','str','num','jsonata',previousValueType]});
var finalspan = $('<span/>',{style:"float: right;margin-top: 6px;"}).appendTo(row);
finalspan.append(' &#8594; <span class="node-input-rule-index">'+(i+1)+'</span> ');
var caseSensitive = $('<input/>',{id:"node-input-rule-case-"+i,class:"node-input-rule-case",type:"checkbox",style:"width:auto;vertical-align:top"}).appendTo(row2);
$('<label/>',{for:"node-input-rule-case-"+i,style:"margin-left: 3px;"}).text(caseLabel).appendTo(row2);
selectField.change(function() {
resizeRule(container);
var type = selectField.val();
if ((type === "btwn") || (type === "index")) {
valueField.typedInput('hide');
expValueField.typedInput('hide');
numValueField.typedInput('hide');
btwnValueField.typedInput('show');
} else if ((type === "head") || (type === "tail")) {
btwnValueField.typedInput('hide');
btwnValue2Field.typedInput('hide');
expValueField.typedInput('hide');
numValueField.typedInput('show');
valueField.typedInput('hide');
} else if (type === "jsonata_exp") {
btwnValueField.typedInput('hide');
btwnValue2Field.typedInput('hide');
expValueField.typedInput('show');
numValueField.typedInput('hide');
valueField.typedInput('hide');
} else {
btwnValueField.typedInput('hide');
expValueField.typedInput('hide');
numValueField.typedInput('hide');
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
valueField.typedInput('hide');
} else {
valueField.typedInput('show');
}
}
if (type === "regex") {
row2.show();
row3.hide();
} else if ((type === "btwn") || (type === "index")) {
row2.hide();
row3.show();
btwnValue2Field.typedInput('show');
} else {
row2.hide();
row3.hide();
}
});
selectField.val(rule.t);
if ((rule.t == "btwn") || (rule.t == "index")) {
btwnValueField.typedInput('value',rule.v);
btwnValueField.typedInput('type',rule.vt||'num');
btwnValue2Field.typedInput('value',rule.v2);
btwnValue2Field.typedInput('type',rule.v2t||'num');
} else if ((rule.t === "head") || (rule.t === "tail")) {
numValueField.typedInput('value',rule.v);
numValueField.typedInput('type',rule.vt||'num');
} else if (rule.t === "jsonata_exp") {
expValueField.typedInput('value',rule.v);
expValueField.typedInput('type',rule.vt||'jsonata');
} else if (typeof rule.v != "undefined") {
valueField.typedInput('value',rule.v);
valueField.typedInput('type',rule.vt||'str');
}
if (rule.case) {
caseSensitive.prop('checked',true);
} else {
caseSensitive.prop('checked',false);
}
selectField.change();
var currentOutputs = JSON.parse(outputCount.val()||"{}");
currentOutputs[opt.hasOwnProperty('i')?opt.i:opt._i] = i;
outputCount.val(JSON.stringify(currentOutputs));
},
removeItem: function(opt) {
var currentOutputs = JSON.parse(outputCount.val()||"{}");
if (opt.hasOwnProperty('i')) {
currentOutputs[opt.i] = -1;
} else {
delete currentOutputs[opt._i];
}
var rules = $("#node-input-rule-container").editableList('items');
rules.each(function(i) {
$(this).find(".node-input-rule-index").html(i+1);
var data = $(this).data('data');
currentOutputs[data.hasOwnProperty('i')?data.i:data._i] = i;
});
outputCount.val(JSON.stringify(currentOutputs));
},
resizeItem: resizeRule,
sortItems: function(rules) {
var currentOutputs = JSON.parse(outputCount.val()||"{}");
var rules = $("#node-input-rule-container").editableList('items');
rules.each(function(i) {
$(this).find(".node-input-rule-index").html(i+1);
var data = $(this).data('data');
currentOutputs[data.hasOwnProperty('i')?data.i:data._i] = i;
});
outputCount.val(JSON.stringify(currentOutputs));
},
sortable: true,
removable: true
});
for (var i=0;i<this.rules.length;i++) {
var rule = this.rules[i];
$("#node-input-rule-container").editableList('addItem',{r:rule,i:i});
}
},
oneditsave: function() {
var rules = $("#node-input-rule-container").editableList('items');
var ruleset;
var node = this;
node.rules = [];
rules.each(function(i) {
var ruleData = $(this).data('data');
var rule = $(this);
var type = rule.find("select").val();
var r = {t:type};
if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else")) {
if ((type === "btwn") || (type === "index")) {
r.v = rule.find(".node-input-rule-btwn-value").typedInput('value');
r.vt = rule.find(".node-input-rule-btwn-value").typedInput('type');
r.v2 = rule.find(".node-input-rule-btwn-value2").typedInput('value');
r.v2t = rule.find(".node-input-rule-btwn-value2").typedInput('type');
} else if ((type === "head") || (type === "tail")) {
r.v = rule.find(".node-input-rule-num-value").typedInput('value');
r.vt = rule.find(".node-input-rule-num-value").typedInput('type');
} else if (type === "jsonata_exp") {
r.v = rule.find(".node-input-rule-exp-value").typedInput('value');
r.vt = rule.find(".node-input-rule-exp-value").typedInput('type');
} else {
r.v = rule.find(".node-input-rule-value").typedInput('value');
r.vt = rule.find(".node-input-rule-value").typedInput('type');
}
if (type === "regex") {
r.case = rule.find(".node-input-rule-case").prop("checked");
}
}
node.rules.push(r);
});
this.propertyType = $("#node-input-property").typedInput('type');
},
oneditresize: function(size) {
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
var height = size.height;
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-rule-container").editableList('height',height);
}
});
})();
</script>