The Custom Layout Component in Form.io

I am currently working with Form.io v3.27.1 and I'm in the process of developing a custom layout component, specifically an accordion. I have been referring to the concepts outlined in the CheckMatrix component example as my guide.

Successfully, I have managed to add the accordion component to the toolbox, drag it onto the form, configure it with a custom edit form, and save it. This results in a Bootstrap themed accordion that displays perfectly.

Nevertheless, the issue lies in the fact that the accordion does not enable me to drag and drop other components into the content area, unlike the behavior of other layout components such as Tabs, Columns, and Fieldset.

After reviewing the source code of other layout controls, I believe that I may need to extend NestedComponent instead of

BaseComponent</code. However, so far, I have been unable to make this adjustment work as intended.</p>

<p>It seems like there might be a small detail that I am overlooking. I just cannot seem to figure out how to create a layout component that will accept other Form.io components as its children.</p>

<p>If anyone has a functional example or any suggestions that I could try to resolve this issue, I would greatly appreciate your help!</p>

<p><a href="https://i.sstatic.net/ZNwVt.png" rel="nofollow noreferrer"></a>
<a href="https://i.sstatic.net/O9VfF.png" rel="nofollow noreferrer"></a></p>

<p><div>
<div>
<pre class="lang-js"><code>import BaseComponent from 'formiojs/components/base/Base';
import NestedComponent from 'formiojs/components/nested/NestedComponent';
import Components from 'formiojs/components/Components';
import * as editForm from './Accordian.form';

export default class AccordionComponent extends BaseComponent {

  /**
   * Define what the default JSON schema for this component is. We will derive from the BaseComponent
   * schema and provide our overrides to that.
   * @return {*}
   */
  static schema() {
    return BaseComponent.schema({
      type: 'accordion',
      label: 'Sections',
      input: false,
      key: 'accordion',
      persistent: false,
      components: [{
        label: 'Section 1',
        key: 'section1',
        components: []
      }]
    });
  }

  /**
   * Register this component to the Form Builder by providing the "builderInfo" object.
   */
  static get builderInfo() {
    return {
      title: 'Accordion',
      group: 'custom',
      icon: 'fa fa-tasks',
      weight: 70,
      schema: AccordionComponent.schema()
    };
  }

  /**
   * Tell the renderer how to build this component using DOM manipulation.
   */
  build() {

    this.element = this.ce('div', {
      class: `form-group formio-component formio-component-accordion ${this.className}`
    }, [
      this.ce('app-formio-accordian', {
          components: JSON.stringify(this.component.components)
        })
    ]);
  }

  elementInfo() {
    return super.elementInfo();
  }

  getValue() {
    return super.getValue();
  }

  setValue(value) {
    super.setValue(value);
  }
}

// Use the table component edit form.
AccordionComponent.editForm = editForm.default;

// Register the component to the Formio.Components registry.
Components.addComponent('accordion', AccordionComponent);
<div class="accordion" id="formioAccordionPreview" *ngIf="components">
    <div class="card" *ngFor="let component of components; first as isFirst">
        <div class="card-header" id="heading-{{component.key}}">
            <h2 class="mb-0">
                <button type="button" class="btn btn-link" data-toggle="collapse" data-target="#collapse-{{component.key}}">{{component.label}}</button>
            </h2>
        </div>
        <div id="collapse-{{component.key}}" class="collapse" [class.show]="isFirst" aria-labelledby="heading-{{component.key}}" data-parent="#formioAccordionPreview">
            <div class="card-body">
                <p>I should be able to 'Drag and Drop a form component' here.</p>
            </div>
        </div>
    </div>
</div>

Answer №1

An Accordion is similar in functionality to a tabs control, providing headered content for easy switching and selection. To create an accordion control, the approach was to extend the existing TabsComponent from Form.io and override the createElement method for element construction using DOM manipulation. Additional overrides for schema and builderInfo were implemented to provide metadata to the FormBuilder, resulting in a fully functional accordion.

accordion.js

import TabsComponent from 'formiojs/components/tabs/Tabs';
import * as editForm from './Accordian.form';

export default class AccordionComponent extends TabsComponent {

  // Schema definition for the Accordion component.
  static schema() {
    return TabsComponent.schema({
      type: 'accordion',
      label: 'Sections',
      input: false,
      key: 'accordion',
      persistent: false,
      components: [{
        label: 'Section 1',
        key: 'section1',
        type: 'tab',
        components: []
      }]
    });
  }

  // Builder information for registering the Accordion component with the Form Builder.
  static get builderInfo() {
    return {
      title: 'Accordion',
      group: 'custom',
      icon: 'fa fa-tasks',
      weight: 70,
      schema: AccordionComponent.schema()
    };
  }

  // Method to construct the Accordion component using DOM manipulation.
  createElement() {
    // Implementation details for creating the Accordion component go here...
  }

  // Method to set the active tab of the Accordion component.
  setTab(index, state) {
    // Implementation details for setting the active tab go here...
  }
}

AccordionComponent.editForm = editForm.default;

The configuration for the Accordion component in the edit form includes custom elements for the Display tab:

Accordion.edit.display.js

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _default = [{
  key: 'components',
  type: 'datagrid',
  input: true,
  label: 'Sections',
  weight: 50,
  reorder: true,
  components: [{
    // Definition of custom fields for the Display tab goes here...
  }]
}];
exports.default = _default;

The form definition override file references the custom Display Tab elements:

Accordion.form.js

"use strict";

require("core-js/modules/es.array.concat");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = _default;

var _NestedComponent = _interopRequireDefault(require("../../../../../../../../../node_modules/formiojs/components/nested/NestedComponent.form"));

var _AccordianEdit = _interopRequireDefault(require("./editForm/Accordian.edit.display"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

// Details for overriding the form structure with custom display components go here...

}

The file structure for the Accordion component is organized as shown in the diagram:

https://i.sstatic.net/52Bb6.jpg

To integrate the Accordion component into an Angular Project, it needs to be registered in the app.component.ts file:

app.component.ts

import { Component } from '@angular/core';
import { Formio } from 'formiojs';
import AccordionComponent from './path/to/your/AccordionComponent';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  title = 'app';

  constructor() {
    Formio.registerComponent('accordion', AccordionComponent);
  }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What methods can I use to prevent a number from becoming negative?

I am currently developing a game where players collect resources, spend them to create more resources, and engage in various activities. However, I'm facing an issue where the resource count goes into negative numbers when too much of a certain item i ...

Exploring the Bounds of Angular CDK Overlay Positioning

As I delved into the world of Angular CDK and its Overlay feature, a question arose within me: Is it possible to utilize the position-builder in a way that confines its scope to a specific container? Instead of positioning elements globally across the en ...

Event triggered when a Socket.IO connection is disconnected

Everything seems to be working well with my code when a new user joins, but I'm encountering issues when a user leaves as it doesn't display anything. Can someone point out the error in my code? Below is my server-side code: const express = requ ...

What is the best way to split an array into four columns and allocate 10 <li> items from the array to each column?

If I have a dynamic array with 40 elements that changes on every render, how can I loop through the array and return 4 groups of elements, each containing 10 items without any repetition? I want to style these objects in the array using a parent flex con ...

Ways to access information and functions from another component

Creating a timer dashboard where alarms can change the background color of the timer. Looking to display the timer on a different page with the set background color from the dashboard, but struggling to pass data between components successfully. http ...

What is the process for retrieving an object from a node.js server using JSON.stringify() in a JavaScript file?

Looking to organize my code, I want to separate the JavaScript from my page and link it through a file instead of having it embedded within script tags. The issue arises when I receive a "SyntaxError: expected expression, got '<' " in Firefox ...

Exploring Javascript: Understanding the Contrast Between Function and Class

In June 2015, with the introduction of ECMAScript 6, a new syntax for Javascript classes was unveiled. The new syntax is exemplified by the following code snippet: class Polygon { constructor(width, height) { this.width = width; thi ...

TS6054: The file 'app/app.ts.' is using an invalid extension

Error Message: "error TS6054: File 'app/app.ts.' has an unsupported extension. The only supported extensions are '.ts', '.tsx', '.d.ts', '.cts', '.d.cts', '.mts', '.d.mts' ...

Getting started with WebTorrent: A beginner's guide

I have been brainstorming some ideas for using WebTorrent. While I am comfortable with JavaScript and jQuery, I have never ventured into Node.js or Browserify territory. Can someone guide me through how to implement the following straightforward code? var ...

Looking to fill a text field with email addresses by selecting checkboxes for various towers

I have 4 towers and I need checkboxes for each of them. Upon checking any combination of them, the 'txtNotifTo' textbox should be populated with the respective group of email IDs for each tower selected. Could you please assist me in identifying ...

The Select element in angular fails to choose the intended option when tested with Cypress

I am encountering a challenge in my automation testing project where I need to select an option from a select element. The project is built using Angular and I am using Cypress for automation testing. As a newcomer to Cypress, I followed the instructions ...

What is the best way to use AJAX to load a PHP file as a part

I'm exploring different methods for making an AJAX call with an included file. Let's create a simple working example. Initially, I have my main index.php file which contains the following content. In this file, I aim to access all the data retur ...

How can I update a property within an object in a sequential manner, similar to taking turns in a game, using React.js?

I am currently working on a ReactJs project where I am creating a game, but I have encountered an issue. I need to alternate turns between players and generate a random number between 1 and 10 for each player, storing this random number inside their respec ...

Employing the identical directive within a directive [angularjs]

My requirement is to utilize the same directive within another directive, based on a conditional parameter. However, every time I attempt to do this, it appears to enter an infinite loop. It seems like the templates are being preloaded and causing a recurs ...

AJax request is failing to execute - Different page is being displayed

Today, I decided to delve into the world of AJAX and started learning the basics. I attempted to create a simple AJAX call, but instead of receiving a response asynchronously, it just loaded the PHP script page as if it was a normal form submission. html ...

Manipulating specific elements within a MongoDB document's array using index values in a Node.js environment

Is there a way to increase the value of a specific element in an array within a MongoDB document using Meteor or nodeJS? For example, consider the following document: { "_id" : "4tP6ewe4Z5kwYA3Je", "name" : "chutia", "address" : "shonir akhra ...

Next.js version 10.0.5 presents an issue with undefined environment variables

I recently started building a website using Next.js and encountered an issue while trying to integrate Google Tag Manager. Despite following the tutorial provided in the Next.js Github example, I found myself unable to access my environment variables. ...

Tips on streamlining two similar TypeScript interfaces with distinct key names

Presented here are two different formats for the same interface: a JSON format with keys separated by low dash, and a JavaScript camelCase format: JSON format: interface MyJsonInterface { key_one: string; key_two: number; } interface MyInterface { ...

Populating multiple divs with the same id using Ajax and JSON data

My ajax request is retrieving a JSON value from a PHP page, like so: {"Inboxunreadmessage":4} Below is the JavaScript I am using for my ajax request: $(window).load(function(){ $.ajax({ dataType: "json", type: "POST", url: "/ ...