Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Custom Components not triggering inside Sub forms #1099

Open
1 task done
ps91 opened this issue Sep 13, 2024 · 3 comments
Open
1 task done

[BUG] Custom Components not triggering inside Sub forms #1099

ps91 opened this issue Sep 13, 2024 · 3 comments
Assignees
Labels

Comments

@ps91
Copy link

ps91 commented Sep 13, 2024

Environment

Please provide as many details as you can:

  • Hosting type
    • [] Form.io
    • Local deployment
  • Formio Angular version: 5.5.0-rc.11
  • Frontend framework: Angular

Steps to Reproduce

  1. Create two forms: in one form, add a data grid component and place a custom component in one of the columns. In the second form, reference the first form.

Expected behavior

The custom component should display correctly.

Observed behavior

The custom component does not display, and after debugging, I found that the custom component is never triggered.

@ps91 ps91 added the bug label Sep 13, 2024
@lane-formio
Copy link
Contributor

Could you possibly provide a codesandbox to illustrate what you are experiencing? Also, what version of formio.js are you using?

@Aduthraes
Copy link

Aduthraes commented Sep 17, 2024

I was about to open a ticket with the same issue.
I'm using:

  • "formiojs": "^4.20.0"
  • "@formio/angular": "5.5.0-rc.11"

Custom components work well in forms, subforms, inside datagrids in the main form, but when you add the custom component to a datagrid and reference that form as a subform inside another form, the custom component is not rendered.
If you put a breakpoint in the ngOnChanges event of the custom component, you'll see that the breakpoint isn't hit.

Ex:
listbox.component.html

<div class="mb-3">
  <p-listbox [options]="items" [(ngModel)]="selected" [optionLabel]="bindLabel" [optionValue]="bindValue"
    [filter]="filter" [multiple]="multipleValues" [checkbox]="checkbox" [disabled]="disabled"
    [listStyle]="{'max-height':maxHeight}" (onChange)="onChange($event)">
  </p-listbox>
</div>

listbox.component.ts

import { Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core';
import { ApiService } from '@hyperflowX/core';
import { FormioCustomComponent } from '@formio/angular';

@Component({
  selector: 'hfx-listbox',
  templateUrl: './listbox.component.html',
  styleUrls: ['./listbox.component.scss']
})
export class ListboxComponent implements FormioCustomComponent<any> {

  @Input() value: any;
  @Output() valueChange = new EventEmitter<any>();
  @Input() disabled = false;
  @Input() label: string;
  @Input() bindLabel: string;
  @Input() bindValue: string;
  @Input() endpoint: string;
  @Input() method: string;
  @Input() dataPath: string;

  @Input() checkbox: boolean;
  @Input() multipleValues: boolean;
  @Input() filter: boolean;
  @Input() maxHeight: string;

  selected: any;
  items = [];

  constructor(private apiService: ApiService<any>) {
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.endpoint) {
      this.getData();
    }

    if (this.value) {
      if (this.multipleValues) {
        // To avoid the validation error "value must not be an array" the value can't be an array 
        // so the value is saved in an object in the property 'items'
        this.selected = this.value.items || [];
      } else {
        this.selected = this.value;
      }
    }
  }

  onChange(data) {
    this.updateValue(this.selected);
  }

  updateValue(newValue: any) {
    // To avoid the validation error "value must not be an array" the value can't be an array 
    // so the value is saved in an object in the property 'items'
    this.value = this.multipleValues ? { items: newValue } : newValue;
    this.valueChange.emit(this.value);
  }

  getData(data?: any) {
    if (this.endpoint) {
      this.apiService.getData(this.method, this.endpoint, null)
        .subscribe(response => {
          if (response) {
            this.items = this.dataPath && response.data[this.dataPath]
              ? response.data[this.dataPath]
              : response.data;
          }
        });
    } else {
      let itemsVariable = this.dataPath ? data[this.dataPath] : data;

      // Verifica se a propriedade é composta
      if (this.dataPath.includes('.')) {
        const props = this.dataPath.split('.');
        let value = data;
        for (const prop of props) {
          value = value[prop];
        }
        itemsVariable = value;
      }

      if (itemsVariable != null) {
        this.items = [...itemsVariable];
      }
    }
  }
}

listbox.formio.ts

import { Injector } from '@angular/core';
import { FormioCustomComponentInfo, registerCustomFormioComponent, Components } from '@formio/angular';
import { ListboxComponent } from './listbox.component';

const COMPONENT_OPTIONS: FormioCustomComponentInfo = {
  type: 'listbox', // custom type. Formio will identify the field with this type.
  selector: 'hfx-listbox', // custom selector. Angular Elements will create a custom html tag with this selector
  title: 'Listbox', // Title of the component
  group: 'basic', // Build Group
  icon: 'fa fa-dot-circle-o', // Icon
  editForm: customEditForm,
  //  template: 'input', // Optional: define a template for the element. Default: input
  //  changeEvent: 'valueChange', // Optional: define the changeEvent
  //  when the formio updates the value in the state. Default: 'valueChange'
  //  editForm: Components.components.textfield.editForm,
  // Optional: define the editForm of the field. Default: the editForm of a textfield
  //  documentation: '', // Optional: define the documentation of the field
  //  weight: 0, // Optional: define the weight in the builder group
  //  schema: {}, // Optional: define extra default schema for the field
  //  extraValidators: [], // Optional: define extra validators  for the field
  //  emptyValue: null, // Optional: the emptyValue of the field
};

export function registerListboxComponent(injector: Injector) {
  try {
    registerCustomFormioComponent(COMPONENT_OPTIONS, ListboxComponent, injector);
  } catch (error) {
    // already registered
  }
}

export function customEditForm() {
  const form = Components.components.textfield.editForm();

  form.components[0].components.push(
    {
      label: 'CONFIG',
      key: 'selectConfiguration',
      weight: 30,
      components: [
        {
          weight: 10,
          type: 'textfield',
          key: 'customOptions.endpoint',
          label: 'Endpoint Url',
          placeholder: 'Endpoint',
          input: true,
        }, {
          weight: 20,
          type: 'select',
          key: 'customOptions.method',
          label: 'Method',
          input: true,
          dataSrc: 'values',
          data: {
            values: [
              { value: 'get', label: 'Get' },
              { value: 'post', label: 'Post' },
            ],
          },
        }, {
          weight: 30,
          type: 'textfield',
          key: 'customOptions.dataPath',
          label: 'Data Path',
          placeholder: 'Data Path',
          tooltip: 'The property within the source data where iterable items reside. For example: result.items',
          input: true,
        }, {
          weight: 40,
          type: 'textfield',
          key: 'customOptions.bindLabel',
          label: 'Bind Label',
          placeholder: 'Bind Label',
          input: true,
          validate: {
            required: true,
          },
        }, {
          weight: 50,
          type: 'textfield',
          key: 'customOptions.bindValue',
          label: 'Bind Value',
          placeholder: 'Bind Value',
          input: true,
          validate: {
            required: true,
          },
        }, {
          weight: 60,
          type: 'checkbox',
          key: 'customOptions.filter',
          label: 'Filter',
          input: true,
        }, {
          weight: 70,
          type: 'checkbox',
          key: 'customOptions.multipleValues',
          label: 'Multiple values',
          input: true,
        }, {
          weight: 80,
          type: 'checkbox',
          key: 'customOptions.checkbox',
          label: 'Checkbox',
          input: true,
          'conditional': {
            'eq': 'true',
            'when': 'customOptions.multipleValues',
            'show': 'true',
          },
        }, {
          weight: 90,
          type: 'textfield',
          key: 'customOptions.maxHeight',
          label: 'Max height',
          tooltip: 'Max height css style',
          input: true
        }
      ],
    });

  return form;
}

Create a form, add a datagrid and add the component the datagrid.
Create a second form and reference the previous form as a subform.

@lane-formio
Copy link
Contributor

If we had a reproducible example from something like stackblitz it would help me get a meaningful review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants