Readonly the whole form except specific fields (Angular)

Hi all,
I use the [readonly]="readonly" property on jsonforms to disable all fields of a form. Note that it is a big form consisting of 40 fields.

Is there any way to disable the 37 fields of the form using the [readonly]="readonly" and leave 3 specific fields enabled?

Do you have any other recommendation to have these 3 fields enabled the whole time.

The evaluation order does not allow to have a specific field enabled if the readonly is specified.

I tried a listener that removes the disabled attribute from DOM but it does not work.
It does not work because the onChange method updates the state of core

Hi @sakis_aj,

Yes, as you noticed, the readonly on the JSON Forms root will disable all elements.

However that disablement is just a “hint” and each renderer itself determines what it means that they are disabled. So what you can do is to use a custom renderer which just ignores this hint for the 3 fields you still want to see enabled.

However overall it seems weird to me to specify that a form shall be read only but still allow modifications. Is there a reason you don’t want to mark all fields which are read only as such in the JSON Schema?

Hi @sdirix

Thanks for your reply.
I have a lot of forms that contain 3 common fields. The rest of them depend on the specific form. Each form represents an item (e.g., vehicle). Before a vehicle is ready the form is not readonly. Once the vehicle is ready for production, no changes in the form should be allowed.
However, the vehicle has a license plate that we should be able to change. This is one example.

I created a custom renderer but it does not work. The reason is that onChange event the jsonformservice calls the updateCore method and the state of form is changed. So the readonly is triggered again.

So, the user can enter just a character in the custom renderer input but then the input becomes readonly again.

This is my current implementation but it does not work.

custom-ctip-name-control.renderer.html

<mat-form-field [fxHide]="hidden" fxFlex>
  <mat-label>{{ label }}</mat-label>
  <input [formControl]="form" [id]="id" [type]="getType()"
         (input)="onChange($event)" matInput/>
  <mat-hint *ngIf="shouldShowUnfocusedDescription()">{{ description }}</mat-hint>
  <mat-error>{{ error }}</mat-error>
</mat-form-field>

custom-ctip-name-control.renderer.ts

import {ChangeDetectionStrategy, Component} from '@angular/core';
import {JsonFormsAngularService, JsonFormsControl} from '@jsonforms/angular';
import {Actions, and, CoreActions, coreReducer, isStringControl, not, or, RankedTester, rankWith, scopeEndsWith} from '@jsonforms/core';


export const customCTIPNameControlTester: RankedTester = rankWith(5,
  and(isStringControl, scopeEndsWith('CTIP_name')));

@Component({
  selector: 'CustomCTIPNameControlRenderer',
  templateUrl: './custom-ctip-name-control.renderer.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
// tslint:disable-next-line:component-class-suffix
export class CustomCTIPNameControlRenderer extends JsonFormsControl {
  constructor(jsonformsService: JsonFormsAngularService) {
    super(jsonformsService);
    this.waitForElm('[id*="CTIP_name"]').then((elm) => {
      document.querySelector('[id*="CTIP_name"]').removeAttribute('disabled');
    });
  }

  waitForElm(selector) {
    return new Promise(resolve => {
      if (document.querySelector(selector)) {
        return resolve(document.querySelector(selector));
      }

      const observer = new MutationObserver(mutations => {
        if (document.querySelector(selector)) {
          resolve(document.querySelector(selector));
          observer.disconnect();
        }
      });

      observer.observe(document.body, {
        childList: true,
        subtree: true
      });
    });
  }

  getEventValue = (event: any) => (event.target.value ? event.target.value : undefined);

  getType = (): string => {
    if (this.uischema.options && this.uischema.options.format) {
      return this.uischema.options.format;
    }
    if (this.scopedSchema && this.scopedSchema.format) {
      switch (this.scopedSchema.format) {
        case 'email':
          return 'email';
        case 'tel':
          return 'tel';
        default:
          return 'text';
      }
    }
    return 'text';
  }

  // onChange(ev: any) {
  //   this.updateCore(
  //     Actions.update(this.propsPath, () => this.getEventValue(ev))
  //   );
  //   this.triggerValidation();
  // }
  //
  // updateCore<T extends CoreActions>(coreAction: T): T {
  //   const coreState = coreReducer(this.jsonFormsService.getState().jsonforms.core, coreAction);
  //   if (coreState !== this.jsonFormsService.getState().jsonforms.core) {
  //     this.jsonFormsService.setReadonly(false);
  //     console.log(this.jsonFormsService.getState().jsonforms.core.data.CTIP_name);
  //   }
  //   return coreAction;
  // }

  // onChange(ev: any) {
  //    const data = this.jsonFormsService.getState().jsonforms.core.data;
  //    data['CTIP_name'] = this.getEventValue(ev);
  //    this.jsonFormsService.setData(data);
  // }

  // onChange(ev: any) {
  //   this.jsonFormsService.updateCore(
  //     Actions.update(this.propsPath, () => this.getEventValue(ev))
  //   );
  //   this.waitForElm('[id*="CTIP_name"][disabled]').then((elm) => {
  //     document.querySelector('[id*="CTIP_name"][disabled]').removeAttribute('disabled');
  //   });
  // }

}

Hi @sakis_aj,

You don’t need to go through the DOM to manually enable your control again. It should be sufficient to overwrite the isEnabled() method of the JsonFormsAbstractControl and just always return true.

WoW.
Thanks @sdirix
It was easier than I thought

Big thanks!