Form.io 自定义布局组件

Form.io Custom Layout Component

我正在使用 Form.io v3.27.1,我正在尝试创建一个自定义布局组件 - 特别是手风琴 - 我大部分时间都在使用 CheckMatrix 组件示例中提供的概念。

我可以让手风琴组件显示在工具箱中,我可以将它拖到表单上,用自定义编辑表单配置它等等。我可以保存它并呈现 Bootstrap 主题手风琴完美。

然而,它不做的是允许我将其他组件拖放到内容区域,类似于其他布局组件的行为(即 TabsColumnsFieldset等)。

我假设通过浏览其他布局控件的源代码我需要扩展 NestedComponent 来代替 BaseComponent,但我还没有能够做到这一点。

我觉得我忽略了一些小事。我似乎无法弄清楚如何呈现接受其他 Form.io 组件作为子组件的布局组件。

任何人有一个工作示例或建议我可以尝试让它工作吗?提前感谢您的帮助!

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 &apos;Drag and Drop a form component&apos; here.</p>
            </div>
        </div>
    </div>
</div>

Accordion在功能上等同于tabs控件,即headered content方便切换和选择。构造 accordion control 的答案是扩展 Form.io 中内置的 TabsComponent 并覆盖通过 DOM 操作构造元素的 createElement 方法。其他几个覆盖(schemabuilderInfo)将元数据提供回 FormBuildervoila!

accordion.js

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

export default class AccordionComponent extends TabsComponent {

  /**
   * 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 TabsComponent.schema({
      type: 'accordion',
      label: 'Sections',
      input: false,
      key: 'accordion',
      persistent: false,
      components: [{
        label: 'Section 1',
        key: 'section1',
        type: 'tab',
        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 builder how to build this component using DOM manipulation.
   */
  createElement() {
    this.tabs = [];
    this.tabLinks = [];
    this.bodies = [];

    this.accordion = this.ce('div', {
      id: `accordion-${this.id}`
    });

    var _this = this;

    this.component.components.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.component = 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;

手风琴在编辑表单中需要一些不同的配置,所以我还包括了编辑表单的显示选项卡的定义:

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: [{
    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;

然后是引用自定义显示选项卡元素的表单定义覆盖:

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

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',
    components: _AccordianEdit.default
  }]].concat(extend));
}

Accordion component 的文件结构如下所示:

然后我只需要在我的 Angular 项目中注册该组件:

app.component.ts

import { Component } from '@angular/core';
import { Formio } from 'formiojs';
import AccordionComponent from './modules/utility/form-shell/cap-forms/cap-form-designer/components/accordian/accordian';

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

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