Password Format without UI Schema

Is it possible for the generated UI Schema to mask field inputs with a format: 'password' or will I need to create an accompanying UI Schema?

I believe just having the format: 'password' defined for a property in my JSON Schema does not work, and adding a UI Schema that defines the control like below does produce the right behavior for the password field, but causes the rest of the form to no longer be generated.

       uischema={{
          type: "Control",
          scope: "#/properties/credentials/properties/token",
          options: {
            format: "password"
          }
        }}

The use case is many forms derived from schemas with differing names for the password field. Ideally I would like to avoid writing an accompanying UI Schema for each schema as so far the generated UI Schema works fine - with the exception of the unmasked password fields. If this is not possible, is there a way to add a few Controls to a generated UI schema?

TLDR: Can I get the generated VerticalLayout to mask password fields without needing to create a full UI Schema for each JSON Schema.

Looks like I can generate the UI Schema manually from a schema using Generate, although it seems to be a summarized and flattened version of the actual UI Schema (my JSON Schemas can be nested).

So I would need to copy the generate code in order to output the full UI Schema, and modify the Control(s) containing a password, and store/load that along with my JSON schema. I could also dynamically generate a UI Schema at render time and swap out credentials based off the property format, but i’d rather not have to maintain a second copy of the the generator code.

Is this the right approach? Or should I be looking into registering additional types/formats or a custom renderer?

Hi @nics3128, thanks for your interest in JSON Forms. At the moment it’s not possible to customize the UI Schema generation, the only option here is to fully generate UI Schema beforehand (which for your use case includes the generation of “nested” UI Schemas) or using the UI Schema registry with partially generated UI Schemas.

Both of these approaches are pretty heavy for your current use case. Therefore I would like to suggest to register a custom PasswordControl renderer. Implementation wise the PasswordControl can just reuse the current MaterialTextControl but already hand over a copy of the UI Schema with options: { format: 'password' } set. Note that you should memoize the adapted UISchema to avoid triggering a rerender on every render pass.

Assuming you have format: 'password' set for string properties in JSON Schema the code should roughly look like this:

import { Unwrapped } from '@jsonforms/material-renderers'
import { rankWith, and, isStringControl, formatIs } from '@jsonforms/core';
import { withJsonFormsControlProps } from '@jsonforms/react';

const { MaterialStringControl } = Unwrapped;

const PasswordControl = (props) => {
  const modifiedUiSchema = useMemo(() => ({ ...props.uischema, options: { ...props.uischema.options, format: 'password'}}), [props.uischema]);
  return <MaterialStringControl {...props} uischema={modifiedUiSchema} />
}

const BoundPasswordControl = withJsonFormsControlProps(PasswordControl);

export const registryEntry = {
  renderer: BoundPasswordControl,
  tester: rankWith(2, and(isStringControl, formatIs('password'));
}

Then add this registryEntry to the renderers you hand over to JSON Forms and you’re set. Also memoize the renderers or define them statically.

1 Like

This worked really well! Much appreciated.

@sdirix i’ll update this reply to clean it up if you update your initial snippet to my small fix below.

I did end up creating basic UI Schemas for each JSON schema using

import { Generate } from "@jsonforms/react";

Generate.uiSchema(schema)

and was adding the nested password format to each as needed, but had only gotten through a few so this will save me significant time. For anyone coming across this, it turns out generating basic UI Schemas for each JSON Schema and incorporating the plumbing for getting those into the JsonForms component was worthwhile as I have already run into a few other edge cases where having a UI Scheme for positioning, formatting, etc. is nice to have.

Additionally, anyone trying this out should note a couple Syntax Errors in the above snippet. Below is my working code, and to help anyone running into a similar issue with custom renderers, the error I got was

Error: Element type is invalid

which comes from trying to destructure a non existent key in the Unwrapped import

const { MaterialStringControl } = Unwrapped;

I checked what keys existed in the Unwrapped import and noticed that MaterialStringControl should be MaterialTextControl.

Super basic, but the error message was a little obscure, so this may save someone 20 mins and improve the forum search for similar issues!

import { useMemo } from "react";
import { Unwrapped } from '@jsonforms/material-renderers'
import { rankWith, and, isStringControl, formatIs } from '@jsonforms/core';
import { withJsonFormsControlProps } from '@jsonforms/react';

const { MaterialTextControl } = Unwrapped;

const PasswordControl = (props) => {
  const modifiedUiSchema = useMemo(() => ({ ...props.uischema, options: { ...props.uischema.options, format: 'password'}}), [props.uischema]);
  return <MaterialTextControl {...props} uischema={modifiedUiSchema} />
}

const BoundPasswordControl = withJsonFormsControlProps(PasswordControl);

export const passwordRegistryEntry = {
  renderer: BoundPasswordControl,
  tester: rankWith(2, and(isStringControl, formatIs('password'))),
}

...

// In component using JSON Forms

...
import { materialRenderers } from "@jsonforms/material-renderers";
import { JsonForms } from "@jsonforms/react";
import { passwordRegistryEntry } from "./JSONFormPassword";
...

const renderers = [...materialRenderers, passwordRegistryEntry];

...

  return (
      ...
      <JsonForms
        ...
        renderers={renderers}
        ...
      />
      ...
  );