最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Form.io Custom Layout Component - Stack Overflow

programmeradmin1浏览0评论

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 &apos;Drag and Drop a form ponent&apos; 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 &apos;Drag and Drop a form ponent&apos; 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
Add a ment  | 

1 Answer 1

Reset to default 4

An 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);
  }
}

发布评论

评论列表(0)

  1. 暂无评论