I am using Form.io v3.27.1, and I am trying to create a custom layout ponent - specifically an accordion - and I'm using the concepts provided in the CheckMatrix ponent example for the most part.
I am able to make the accordion ponent show in the toolbox, and I'm able to drag it onto the form, configure it with a custom edit form etc. I can save it and it renders a Bootstrap themed accordion perfectly.
What it does NOT do however, is allow me to drag and drop other ponents into the content area similar to the behavior of other layout ponents (i.e. Tabs
, Columns
, Fieldset
etc.).
I assume by skimming through the source code of the other layout controls that I need to extend NestedComponent
in lieu of BaseComponent
, but I've not yet been able to make that work.
I feel like I am overlooking something small. I just can't seem to figure out how to render a layout ponent that accepts other Form.io ponents as children.
Anyone have a working example or suggestions I can try to get this working? I appreciate your help in advance!
import BaseComponent from 'formiojs/ponents/base/Base';
import NestedComponent from 'formiojs/ponents/nested/NestedComponent';
import Components from 'formiojs/ponents/Components';
import * as editForm from './Accordian.form';
export default class AccordionComponent extends BaseComponent {
/**
* Define what the default JSON schema for this ponent 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,
ponents: [{
label: 'Section 1',
key: 'section1',
ponents: []
}]
});
}
/**
* Register this ponent 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 ponent using DOM manipulation.
*/
build() {
this.element = this.ce('div', {
class: `form-group formio-ponent formio-ponent-accordion ${this.className}`
}, [
this.ce('app-formio-accordian', {
ponents: JSON.stringify(thisponentponents)
})
]);
}
elementInfo() {
return super.elementInfo();
}
getValue() {
return super.getValue();
}
setValue(value) {
super.setValue(value);
}
}
// Use the table ponent edit form.
AccordionComponent.editForm = editForm.default;
// Register the ponent to the Formio.Components registry.
Components.addComponent('accordion', AccordionComponent);
<div class="accordion" id="formioAccordionPreview" *ngIf="ponents">
<div class="card" *ngFor="let ponent of ponents; first as isFirst">
<div class="card-header" id="heading-{{ponent.key}}">
<h2 class="mb-0">
<button type="button" class="btn btn-link" data-toggle="collapse" data-target="#collapse-{{ponent.key}}">{{ponent.label}}</button>
</h2>
</div>
<div id="collapse-{{ponent.key}}" class="collapse" [class.show]="isFirst" aria-labelledby="heading-{{ponent.key}}" data-parent="#formioAccordionPreview">
<div class="card-body">
<p>I should be able to 'Drag and Drop a form ponent' here.</p>
</div>
</div>
</div>
</div>
I am using Form.io v3.27.1, and I am trying to create a custom layout ponent - specifically an accordion - and I'm using the concepts provided in the CheckMatrix ponent example for the most part.
I am able to make the accordion ponent show in the toolbox, and I'm able to drag it onto the form, configure it with a custom edit form etc. I can save it and it renders a Bootstrap themed accordion perfectly.
What it does NOT do however, is allow me to drag and drop other ponents into the content area similar to the behavior of other layout ponents (i.e. Tabs
, Columns
, Fieldset
etc.).
I assume by skimming through the source code of the other layout controls that I need to extend NestedComponent
in lieu of BaseComponent
, but I've not yet been able to make that work.
I feel like I am overlooking something small. I just can't seem to figure out how to render a layout ponent that accepts other Form.io ponents as children.
Anyone have a working example or suggestions I can try to get this working? I appreciate your help in advance!
import BaseComponent from 'formiojs/ponents/base/Base';
import NestedComponent from 'formiojs/ponents/nested/NestedComponent';
import Components from 'formiojs/ponents/Components';
import * as editForm from './Accordian.form';
export default class AccordionComponent extends BaseComponent {
/**
* Define what the default JSON schema for this ponent 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,
ponents: [{
label: 'Section 1',
key: 'section1',
ponents: []
}]
});
}
/**
* Register this ponent 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 ponent using DOM manipulation.
*/
build() {
this.element = this.ce('div', {
class: `form-group formio-ponent formio-ponent-accordion ${this.className}`
}, [
this.ce('app-formio-accordian', {
ponents: JSON.stringify(this.ponent.ponents)
})
]);
}
elementInfo() {
return super.elementInfo();
}
getValue() {
return super.getValue();
}
setValue(value) {
super.setValue(value);
}
}
// Use the table ponent edit form.
AccordionComponent.editForm = editForm.default;
// Register the ponent to the Formio.Components registry.
Components.addComponent('accordion', AccordionComponent);
<div class="accordion" id="formioAccordionPreview" *ngIf="ponents">
<div class="card" *ngFor="let ponent of ponents; first as isFirst">
<div class="card-header" id="heading-{{ponent.key}}">
<h2 class="mb-0">
<button type="button" class="btn btn-link" data-toggle="collapse" data-target="#collapse-{{ponent.key}}">{{ponent.label}}</button>
</h2>
</div>
<div id="collapse-{{ponent.key}}" class="collapse" [class.show]="isFirst" aria-labelledby="heading-{{ponent.key}}" data-parent="#formioAccordionPreview">
<div class="card-body">
<p>I should be able to 'Drag and Drop a form ponent' here.</p>
</div>
</div>
</div>
</div>
Share
Improve this question
edited Dec 19, 2019 at 19:59
XamlZealot
asked Dec 19, 2019 at 19:43
XamlZealotXamlZealot
7225 silver badges15 bronze badges
2
- Have you found the answer? – Hamza Khanzada Commented Jan 23, 2020 at 8:21
- 1 I did figure it out, I will post the answer as soon as possible! – XamlZealot Commented Jan 24, 2020 at 13:28
1 Answer
Reset to default 4An Accordion
is functionally identical to a tabs control, that is, headered content
that facilitates switching and selection. The answer to constructing an accordion control
was to extend the TabsComponent
that's built into Form.io
and override the createElement
method that constructs the element via DOM manipulation. A couple of other overrides (schema
and builderInfo
) to provide metadata back to the FormBuilder
and voila!
accordion.js
import TabsComponent from 'formiojs/ponents/tabs/Tabs';
import * as editForm from './Accordian.form';
export default class AccordionComponent extends TabsComponent {
/**
* Define what the default JSON schema for this ponent is. We will derive from the BaseComponent
* schema and provide our overrides to that.
* @return {*}
*/
static schema() {
return TabsComponent.schema({
type: 'accordion',
label: 'Sections',
input: false,
key: 'accordion',
persistent: false,
ponents: [{
label: 'Section 1',
key: 'section1',
type: 'tab',
ponents: []
}]
});
}
/**
* Register this ponent 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 builder how to build this ponent using DOM manipulation.
*/
createElement() {
this.tabs = [];
this.tabLinks = [];
this.bodies = [];
this.accordion = this.ce('div', {
id: `accordion-${this.id}`
});
var _this = this;
this.ponent.ponents.forEach(function (tab, index) {
var isFirst = index === 0;
var tabLink = _this.ce('a', {
class: 'card-link',
data_toggle: 'collapse',
href: `#collapse-${tab.key}`
}, tab.label);
_this.addEventListener(tabLink, 'click', function (event) {
event.preventDefault();
_this.setTab(index);
});
var header = _this.ce('div', {
class: 'card-header'
}, [tabLink]);
var tabPanel = _this.ce('div', {
class: 'tab-pane',
role: 'tabpanel',
tabLink: tabLink
});
var tabContent = _this.ce('div', {
class: 'tab-content'
}, [tabPanel]);
var body = _this.ce('div', {
class: 'card-body',
id: tab.key
}, [tabContent]);
var content = _this.ce('div', {
id: `collapse-${tab.key}`,
class: 'collapse'.concat(isFirst ? ' show' : ''),
data_parent: `#accordion-${_this.id}`
}, [body]);
var card = _this.ce('div', {
class: 'card'
}, [header, body]);
_this.tabLinks.push(header);
_this.tabs.push(tabPanel);
_this.bodies.push(body);
_this.accordion.appendChild(card);
});
if (this.element) {
this.appendChild(this.element, [this.accordion]);
this.element.className = this.className;
return this.element;
}
this.element = this.ce('div', {
id: this.id,
class: this.className
}, [this.accordion]);
this.element.ponent = this;
return this.element;
}
setTab(index, state) {
super.setTab(index, state);
var _this = this;
if (this.bodies) {
this.bodies.forEach(function (body) {
body.style.display = 'none';
});
_this.bodies[index].style.display = 'block';
}
}
}
AccordionComponent.editForm = editForm.default;
The Accordion requires some different configuration in the edit form, so I also included a definition for the Display tab of the edit form:
Accordion.edit.display.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _default = [{
key: 'ponents',
type: 'datagrid',
input: true,
label: 'Sections',
weight: 50,
reorder: true,
ponents: [{
type: 'textfield',
input: true,
key: 'label',
label: 'Label'
}, {
type: 'textfield',
input: true,
key: 'key',
label: 'Key',
allowCalculateOverride: true,
calculateValue: {
_camelCase: [{
var: 'row.label'
}]
}
}]
}];
exports.default = _default;
And then the form definition override that 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/ponents/nested/NestedComponent.form"));
var _AccordianEdit = _interopRequireDefault(require("./editForm/Accordian.edit.display"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _default() {
for (var _len = arguments.length, extend = new Array(_len), _key = 0; _key < _len; _key++) {
extend[_key] = arguments[_key];
}
return _NestedComponent.default.apply(void 0, [[{
key: 'display',
ponents: _AccordianEdit.default
}]].concat(extend));
}
The file structure for the Accordion ponent
looks like this:
Then I just need to register the ponent in my Angular Project:
app.ponent.ts
import { Component } from '@angular/core';
import { Formio } from 'formiojs';
import AccordionComponent from './modules/utility/form-shell/cap-forms/cap-form-designer/ponents/accordian/accordian';
@Component({
selector: 'app-root',
templateUrl: './app.ponent.html'
})
export class AppComponent {
title = 'app';
constructor() {
Formio.registerComponent('accordion', AccordionComponent);
}
}