diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
index 7c4964ca8..47355565f 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js
@@ -358,61 +358,64 @@
{ value: "_session", source: ["websocket out","tcp out"] },
]
var allOptions = {
- msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: msgAutoComplete(msgCompletions)},
- flow: {value:"flow",label:"flow.",hasValue:true,
- options:[],
- validate:RED.utils.validatePropertyExpression,
+ msg: { value: "msg", label: "msg.", validate: RED.utils.validatePropertyExpression, autoComplete: msgAutoComplete(msgCompletions) },
+ flow: { value: "flow", label: "flow.", hasValue: true,
+ options: [],
+ validate: RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport,
valueLabel: contextLabel,
autoComplete: contextAutoComplete
},
- global: {value:"global",label:"global.",hasValue:true,
- options:[],
- validate:RED.utils.validatePropertyExpression,
+ global: {
+ value: "global", label: "global.", hasValue: true,
+ options: [],
+ validate: RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport,
valueLabel: contextLabel,
autoComplete: contextAutoComplete
},
- str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"},
- num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate: function(v) {
- return (true === RED.utils.validateTypedProperty(v, "num"));
+ str: { value: "str", label: "string", icon: "red/images/typedInput/az.svg" },
+ num: { value: "num", label: "number", icon: "red/images/typedInput/09.svg", validate: function (v, o) {
+ return RED.utils.validateTypedProperty(v, "num", o);
} },
- bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.svg",options:["true","false"]},
+ bool: { value: "bool", label: "boolean", icon: "red/images/typedInput/bool.svg", options: ["true", "false"] },
json: {
- value:"json",
- label:"JSON",
- icon:"red/images/typedInput/json.svg",
- validate: function(v) { try{JSON.parse(v);return true;}catch(e){return false;}},
- expand: function() {
+ value: "json",
+ label: "JSON",
+ icon: "red/images/typedInput/json.svg",
+ validate: function (v, o) {
+ return RED.utils.validateTypedProperty(v, "json", o);
+ },
+ expand: function () {
var that = this;
var value = this.value();
try {
- value = JSON.stringify(JSON.parse(value),null,4);
- } catch(err) {
+ value = JSON.stringify(JSON.parse(value), null, 4);
+ } catch (err) {
}
RED.editor.editJSON({
value: value,
stateId: RED.editor.generateViewStateId("typedInput", that, "json"),
focus: true,
- complete: function(v) {
+ complete: function (v) {
var value = v;
try {
value = JSON.stringify(JSON.parse(v));
- } catch(err) {
+ } catch (err) {
}
that.value(value);
}
})
}
},
- re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.svg"},
+ re: { value: "re", label: "regular expression", icon: "red/images/typedInput/re.svg" },
date: {
- value:"date",
- label:"timestamp",
- icon:"fa fa-clock-o",
- options:[
+ value: "date",
+ label: "timestamp",
+ icon: "fa fa-clock-o",
+ options: [
{
label: 'milliseconds since epoch',
value: ''
@@ -431,15 +434,17 @@
value: "jsonata",
label: "expression",
icon: "red/images/typedInput/expr.svg",
- validate: function(v) { try{jsonata(v);return true;}catch(e){return false;}},
- expand:function() {
+ validate: function (v, o) {
+ return RED.utils.validateTypedProperty(v, "jsonata", o);
+ },
+ expand: function () {
var that = this;
RED.editor.editExpression({
- value: this.value().replace(/\t/g,"\n"),
+ value: this.value().replace(/\t/g, "\n"),
stateId: RED.editor.generateViewStateId("typedInput", that, "jsonata"),
focus: true,
- complete: function(v) {
- that.value(v.replace(/\n/g,"\t"));
+ complete: function (v) {
+ that.value(v.replace(/\n/g, "\t"));
}
})
}
@@ -448,13 +453,13 @@
value: "bin",
label: "buffer",
icon: "red/images/typedInput/bin.svg",
- expand: function() {
+ expand: function () {
var that = this;
RED.editor.editBuffer({
value: this.value(),
stateId: RED.editor.generateViewStateId("typedInput", that, "bin"),
focus: true,
- complete: function(v) {
+ complete: function (v) {
that.value(v);
}
})
@@ -470,9 +475,9 @@
value: "node",
label: "node",
icon: "red/images/typedInput/target.svg",
- valueLabel: function(container,value) {
+ valueLabel: function (container, value) {
var node = RED.nodes.node(value);
- var nodeDiv = $('
',{class:"red-ui-search-result-node"}).css({
+ var nodeDiv = $('
', { class: "red-ui-search-result-node" }).css({
"margin-top": "2px",
"margin-left": "3px"
}).appendTo(container);
@@ -481,117 +486,117 @@
"margin-left": "6px"
}).appendTo(container);
if (node) {
- var colour = RED.utils.getNodeColor(node.type,node._def);
- var icon_url = RED.utils.getNodeIcon(node._def,node);
+ var colour = RED.utils.getNodeColor(node.type, node._def);
+ var icon_url = RED.utils.getNodeIcon(node._def, node);
if (node.type === 'tab') {
colour = "#C0DEED";
}
- nodeDiv.css('backgroundColor',colour);
- var iconContainer = $('
',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
+ nodeDiv.css('backgroundColor', colour);
+ var iconContainer = $('
', { class: "red-ui-palette-icon-container" }).appendTo(nodeDiv);
RED.utils.createIconElement(icon_url, iconContainer, true);
- var l = RED.utils.getNodeLabel(node,node.id);
+ var l = RED.utils.getNodeLabel(node, node.id);
nodeLabel.text(l);
} else {
nodeDiv.css({
'backgroundColor': '#eee',
- 'border-style' : 'dashed'
+ 'border-style': 'dashed'
});
}
},
- expand: function() {
+ expand: function () {
var that = this;
RED.tray.hide();
RED.view.selectNodes({
single: true,
selected: [that.value()],
- onselect: function(selection) {
+ onselect: function (selection) {
that.value(selection.id);
RED.tray.show();
},
- oncancel: function() {
+ oncancel: function () {
RED.tray.show();
}
})
}
},
- cred:{
- value:"cred",
- label:"credential",
- icon:"fa fa-lock",
+ cred: {
+ value: "cred",
+ label: "credential",
+ icon: "fa fa-lock",
inputType: "password",
- valueLabel: function(container,value) {
+ valueLabel: function (container, value) {
var that = this;
- container.css("pointer-events","none");
- container.css("flex-grow",0);
+ container.css("pointer-events", "none");
+ container.css("flex-grow", 0);
this.elementDiv.hide();
var buttons = $('
').css({
position: "absolute",
- right:"6px",
+ right: "6px",
top: "6px",
- "pointer-events":"all"
+ "pointer-events": "all"
}).appendTo(container);
var eyeButton = $('
').css({
- width:"20px"
- }).appendTo(buttons).on("click", function(evt) {
+ width: "20px"
+ }).appendTo(buttons).on("click", function (evt) {
evt.preventDefault();
var cursorPosition = that.input[0].selectionStart;
var currentType = that.input.attr("type");
if (currentType === "text") {
- that.input.attr("type","password");
+ that.input.attr("type", "password");
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
- setTimeout(function() {
+ setTimeout(function () {
that.input.focus();
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
- },50);
+ }, 50);
} else {
- that.input.attr("type","text");
+ that.input.attr("type", "text");
eyeCon.removeClass("fa-eye").addClass("fa-eye-slash");
- setTimeout(function() {
+ setTimeout(function () {
that.input.focus();
that.input[0].setSelectionRange(cursorPosition, cursorPosition);
- },50);
+ }, 50);
}
}).hide();
- var eyeCon = $('
').css("margin-left","-2px").appendTo(eyeButton);
+ var eyeCon = $('
').css("margin-left", "-2px").appendTo(eyeButton);
if (value === "__PWRD__") {
var innerContainer = $('
').css({
- padding:"6px 6px",
- borderRadius:"4px"
+ padding: "6px 6px",
+ borderRadius: "4px"
}).addClass("red-ui-typedInput-value-label-inactive").appendTo(container);
- var editButton = $('
').appendTo(buttons).on("click", function(evt) {
+ var editButton = $('
').appendTo(buttons).on("click", function (evt) {
evt.preventDefault();
innerContainer.hide();
- container.css("background","none");
- container.css("pointer-events","none");
+ container.css("background", "none");
+ container.css("pointer-events", "none");
that.input.val("");
that.element.val("");
that.elementDiv.show();
editButton.hide();
cancelButton.show();
eyeButton.show();
- setTimeout(function() {
+ setTimeout(function () {
that.input.focus();
- },50);
+ }, 50);
});
- var cancelButton = $('
').css("margin-left","3px").appendTo(buttons).on("click", function(evt) {
+ var cancelButton = $('
').css("margin-left", "3px").appendTo(buttons).on("click", function (evt) {
evt.preventDefault();
innerContainer.show();
- container.css("background","");
+ container.css("background", "");
that.input.val("__PWRD__");
that.element.val("__PWRD__");
that.elementDiv.hide();
editButton.show();
cancelButton.hide();
eyeButton.hide();
- that.input.attr("type","password");
+ that.input.attr("type", "password");
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
}).hide();
} else {
- container.css("background","none");
- container.css("pointer-events","none");
+ container.css("background", "none");
+ container.css("pointer-events", "none");
this.elementDiv.show();
eyeButton.show();
}
@@ -1538,26 +1543,48 @@
}
}
},
- validate: function() {
- var result;
- var value = this.value();
- var type = this.type();
+ validate: function(options) {
+ let valid = true;
+ const value = this.value();
+ const type = this.type();
if (this.typeMap[type] && this.typeMap[type].validate) {
- var val = this.typeMap[type].validate;
- if (typeof val === 'function') {
- result = val(value);
+ const validate = this.typeMap[type].validate;
+ if (typeof validate === 'function') {
+ valid = validate(value, {});
} else {
- result = val.test(value);
+ // Regex
+ valid = validate.test(value);
+ if (!valid) {
+ valid = RED._("validator.errors.invalid-regexp");
+ }
+ }
+ }
+ if ((typeof valid === "string") || !valid) {
+ this.element.addClass("input-error");
+ this.uiSelect.addClass("input-error");
+ if (typeof valid === "string") {
+ let tooltip = this.element.data("tooltip");
+ if (tooltip) {
+ tooltip.setContent(valid);
+ } else {
+ tooltip = RED.popover.tooltip(this.elementDiv, valid);
+ this.element.data("tooltip", tooltip);
+ }
}
} else {
- result = true;
+ this.element.removeClass("input-error");
+ this.uiSelect.removeClass("input-error");
+ const tooltip = this.element.data("tooltip");
+ if (tooltip) {
+ this.element.data("tooltip", null);
+ tooltip.delete();
+ }
}
- if (result) {
- this.uiSelect.removeClass('input-error');
- } else {
- this.uiSelect.addClass('input-error');
+ if (options?.returnErrorMessage === true) {
+ return valid;
}
- return result;
+ // Must return a boolean for no 3.x validator
+ return (typeof valid === "string") ? false : valid;
},
show: function() {
this.uiSelect.show();
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js
index 90570a969..f11dd45d9 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js
@@ -190,7 +190,10 @@ RED.editor = (function() {
const input = $("#"+prefix+"-"+property);
const isTypedInput = input.length > 0 && input.next(".red-ui-typedInput-container").length > 0;
if (isTypedInput) {
- valid = input.typedInput("validate");
+ valid = input.typedInput("validate", { returnErrorMessage: true });
+ if (typeof valid === "string") {
+ return label ? label + ": " + valid : valid;
+ }
}
}
}
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js
index 94bf19718..cfc7f65de 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js
@@ -901,11 +901,25 @@ RED.utils = (function() {
return parts;
}
- function validatePropertyExpression(str) {
+ /**
+ * Validate a property expression
+ * @param {*} str - the property value
+ * @returns {boolean|string} whether the node proprty is valid. `true`: valid `false|String`: invalid
+ */
+ function validatePropertyExpression(str, opt) {
try {
- var parts = normalisePropertyExpression(str);
+ const parts = normalisePropertyExpression(str);
return true;
} catch(err) {
+ // If the validator has opt, it is a 3.x validator that
+ // can return a String to mean 'invalid' and provide a reason
+ if (opt) {
+ if (opt.label) {
+ return opt.label + ': ' + err.message;
+ }
+ return err.message;
+ }
+ // Otherwise, a 2.x returns a false value
return false;
}
}
@@ -923,22 +937,24 @@ RED.utils = (function() {
// Allow ${ENV_VAR} value
return true
}
- let error
+ let error;
if (propertyType === 'json') {
try {
JSON.parse(propertyValue);
} catch(err) {
error = RED._("validator.errors.invalid-json", {
error: err.message
- })
+ });
}
} else if (propertyType === 'msg' || propertyType === 'flow' || propertyType === 'global' ) {
- if (!RED.utils.validatePropertyExpression(propertyValue)) {
- error = RED._("validator.errors.invalid-prop")
+ // To avoid double label
+ const valid = RED.utils.validatePropertyExpression(propertyValue, opt ? {} : null);
+ if (valid !== true) {
+ error = opt ? valid : RED._("validator.errors.invalid-prop");
}
} else if (propertyType === 'num') {
if (!/^NaN$|^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$|^[+-]?(0b|0B)[01]+$|^[+-]?(0o|0O)[0-7]+$|^[+-]?(0x|0X)[0-9a-fA-F]+$/.test(propertyValue)) {
- error = RED._("validator.errors.invalid-num")
+ error = RED._("validator.errors.invalid-num");
}
} else if (propertyType === 'jsonata') {
try {
@@ -946,16 +962,16 @@ RED.utils = (function() {
} catch(err) {
error = RED._("validator.errors.invalid-expr", {
error: err.message
- })
+ });
}
}
if (error) {
if (opt && opt.label) {
- return opt.label+': '+error
+ return opt.label + ': ' + error;
}
- return error
+ return error;
}
- return true
+ return true;
}
function getMessageProperty(msg,expr) {