Write node forms using Vue 3 and JSON Schema

Soon you will be able to test my framework for writting node forms using Vue 3 and JSON Schema. This will enable the creation of more complex config forms and write code in an modern way.

Directory structure

.
├── src/
│   └── nodes/
│       └── node-1/
│           ├── schemas/
│           │   ├── config.js
│           │   ├── iput.js
│           │   ├── output-1.js
│           │   ├── output-2.js
│           │   ├── ...
│           │   └── output-n.js
│           ├── client/
│           │   ├── locales/
│           │   │   ├── docs/
│           │   │   │   ├── en-US.md
│           │   │   │   ├── de.md
│           │   │   │   └── ...
│           │   │   └── labels/
│           │   │       ├── en-US.json
│           │   │       ├── de.json
│           │   │       └── ...
│           │   ├── assets/
│           │   │   └── icons/
│           │   │       └── vue.svg
│           │   ├── form.vue
│           │   └── index.js
│           └── server/
│               └── index.js
└── package.json

src/nodes/node-1/client/index.js

import MyComponentForm from "./form.vue";
import MyComponentFormSchema from "../schemas/config.js";
import { registerType } from "@nrg/core/client";

export default registerType({
  category: "function",
  color: "#FFFFFF",
  inputs: 1,
  outputs: 1,
  icon: "vue.png", // TODO: check if I can load base64 asset
  form: MyComponentForm, // NOTE: vue form to change node properties
  schema: MyComponentFormSchema, // NOTE: the same source of truth for defaults and credentials in both server and client.
});

src/nodes/node-1/client/form.vue

<template>
  <div>
    <div class="form-row">
      <label><i class="fa fa-tag"></i> Username</label>
      <NodeRedInput
        v-model:value="node.credentials.username"
        :error="errors['node.credentials.username']"
      />
    </div>
    <div class="form-row">
      <label><i class="fa fa-tag"></i> Password</label>
      <NodeRedInput
        v-model:value="node.credentials.password"
        type="password"
        :error="errors['node.credentials.password']"
      />
    </div>
    <div class="form-row">
      <label>Typed Input</label>
      <NodeRedTypedInput
        v-model:value="node.myProperty"
        :types="types"
        :error="errors['node.myProperty']"
      />
    </div>
    <div class="form-row">
      <label>Typed Input 2</label>
      <NodeRedTypedInput
        v-model:value="node.myProperty2"
        :error="errors['node.myProperty2']"
      />
    </div>
    <div class="form-row">
      <label>Config Input</label>
      <NodeRedConfigInput
        v-model:value="node.remoteServer"
        type="remote-server"
        :error="errors['node.remoteServer']"
      />
    </div>
    <div class="form-row">
      <label>Select Input</label>
      <NodeRedSelectInput
        v-model:value="node.country"
        :options="countries"
        :error="errors['node.country']"
      />
    </div>
    <div class="form-row">
      <label>MultiSelect Input</label>
      <NodeRedSelectInput
        v-model:value="node.fruit"
        :options="fruits"
        multiple
        :error="errors['node.fruit']"
      />
    </div>
    <div class="form-row">
      <label>Select Input</label>
      <NodeRedSelectInput
        v-model:value="node.number"
        :options="numbers"
        :error="errors['node.number']"
      />
    </div>
    <div class="form-row">
      <label>Select Input</label>
      <NodeRedSelectInput
        v-model:value="node.object"
        :options="objects"
        multiple
        :error="errors['node.object']"
      />
    </div>
    <div class="form-row">
      <label>Select Input</label>
      <NodeRedSelectInput
        v-model:value="node.array"
        :options="arrays"
        :error="errors['node.array']"
      />
    </div>
    <div class="form-row">
      <label>Editor with default height 200px and JSON</label>
      <NodeRedEditorInput
        v-model:value="node.jsontest"
        :error="errors['node.jsontest']"
      />
    </div>
    <div class="form-row">
      <label>Editor with custom height and CSS</label>
      <NodeRedEditorInput
        v-model:value="node.csstest"
        language="css"
        style="height: 100px"
        :error="errors['node.csstest']"
      />
    </div>
  </div>
</template>

<script>
export default {
  props: {
    node: {
      type: Object,
      required: true,
    },
    errors: {
      type: Object,
      default: () => ({}),
    },
  },
  data() {
    return {
      types: ["str", "msg"],
      countries: [
        { value: "usa", label: "usa" },
        { value: "argentina", label: "argentina" },
        { value: "brasil", label: "brasil" },
      ],
      fruits: [
        { value: "apple", label: "apple" },
        { value: "melon", label: "melon" },
        { value: "raspberry", label: "raspberry" },
      ],
      numbers: [
        { value: "1", label: "1" },
        { value: "2", label: "2" },
        { value: "3", label: "3" },
      ],
      objects: [
        { value: JSON.stringify({ test: "a" }), label: "a" },
        { value: JSON.stringify({ test: "b" }), label: "b" },
        { value: JSON.stringify({ test: "c" }), label: "c" },
      ],
      arrays: [
        { value: JSON.stringify(["a"]), label: "a" },
        { value: JSON.stringify(["b"]), label: "b" },
        { value: JSON.stringify(["c"]), label: "c" },
      ],
    };
  },
};
</script>

<style scoped>
.label {
  width: 100%;
}
</style>

4 Likes

If you want a preview, install this node in your node-red instance.

Node-RED version >= 4.0.9

It is very messy because most of the code there is going to be packaged and integrated during bundling. The only code you will need to provide once I'm done is the schema.js and the form.vue.

This build is still using an old idea I had where you declare config and credentials using decorator called @config. You will notice that Typed Input 2 isn't saved once the node changes are deployed, and the reason is because the server (the source of truth) doesn't have a prop annotated with @config for it. I added that field to demonstrate that the server controls the props and credentials that can be updated from the client.

1 Like

Any suggestions of components you would like to have available besides these ones?