For the Editor, you need 2 HTML elements (if you want to save the value). Then you may need a validation function:
/** Validate a typed input as a string
* Must not be JSON. Can be a number only if allowNum=true. Can be an empty string only if allowBlank=true
* Sets typedInput border to red if not valid since custom validation not available on std typedInput types
* @param {string} value Input value
* @param {string} inpName Input field name
* @param {boolean} allowBlank true=allow blank string. Default=true
* @param {boolean} allowNum true=allow numeric input. Default=false
* @returns {boolean} True if valid
*/
function tiValidateOptString(value, inpName, allowBlank = true, allowNum = false) {
let isValid = true
let f
try {
f = value.slice(0, 1)
} catch (e) {}
if (allowBlank === false && value === '') {
isValid = false
// console.log({ name: inpName, why: 'Blank failed', value: value, allowBlank: allowBlank })
}
if ( allowNum === false && (value !== '' && !isNaN(Number(value))) ) {
isValid = false
// console.log({ name: inpName, why: 'Num failed', value: value, allowNum: allowNum })
}
if ( f === '{' || f === '[' ) isValid = false
$(`#node-input-${inpName} + .red-ui-typedInput-container`).css('border-color', isValid ? 'var(--red-ui-form-input-border-color)' : 'red')
return isValid
}
You also need to prep eaach typed input in oneditprepare
:
// @ts-ignore core data typed input
$('#node-input-data').typedInput({
// types: stdStrTypes,
default: 'msg',
typeField: $('#node-input-dataSourceType'),
}).on('change', function(event, type, value) {
// console.log('change')
if ( type === 'msg' && !this.value ) {
$('#node-input-data').typedInput('value', 'payload')
// console.log('change 2')
}
} ).typedInput('width', tiWidth)
Then you also have to handle things in the runtime as well - with added complexity since you need to be able to handle things as async functions.
// ...
// This now has to be async
async function inputMsgHandler(msg, send, done) {
// ....
// Get all of the typed input values (in parallel)
await Promise.all([
getSource('parent', this, msg, RED),
getSource('elementId', this, msg, RED),
getSource('heading', this, msg, RED),
getSource('data', this, msg, RED), // contains core data
getSource('position', this, msg, RED),
])
// ....
}
// ...
And a processing utility with all the error checking, etc.
/** Get an individual value for a typed input field and save to supplied node object - REQUIRES standardised node property names
* Use propnameSource and propnameSourceType as standard input field names
* @param {string} propName Name of the node property to check
* @param {runtimeNode} node reference to node instance
* @param {*} msg incoming msg
* @param {runtimeRED} RED Reference to runtime RED library
* @param {string} [src] Name of the typed input field (defaults to `${propName}Source`)
* @param {string} [srcType] Name of the field holding the typed input field type (defaults to `${propName}SourceType`)
*/
getSource: async function getSource(propName, node, msg, RED, src, srcType) {
if (!propName) throw new Error('[uiblib:getSource] No propName provided, cannot continue')
if (!node) throw new Error('[uiblib:getSource] No node object provided, cannot continue')
if (!RED) throw new Error('[uiblib:getSource] No RED reference provided, cannot continue')
if (!src) src = `${propName}Source`
if (!srcType) srcType = `${propName}SourceType`
if (!msg && srcType === 'msg') throw new Error('[uiblib:getSource] Type is msg but no msg object provided, cannot continue')
if (!(src in node)) throw Error(`[uiblib:getSource] Property ${src} does not exist in supplied node object`)
if (!(srcType in node)) throw Error(`[uiblib:getSource] Property ${srcType} does not exist in supplied node object`)
const evaluateNodeProperty = promisify(RED.util.evaluateNodeProperty)
if (node[src] !== '') {
try {
if (msg) node[propName] = await evaluateNodeProperty(node[src], node[srcType], node, msg)
else node[propName] = await evaluateNodeProperty(node[src], node[srcType], node)
} catch (e) {
node.warn(`Cannot evaluate source for ${propName}. ${e.message} (${srcType})`)
}
}
},
You will see that I've created some standard utility functions to help reduce my boilerplate otherwise a node with lots of typed inputs gets very bloated. In reality, the getSource
function is in a separate shared library.
Thats quite a lot of work that has to be done for each typed input.