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

Support for custom elements #123

Closed
13 tasks
vasicvuk opened this issue Jul 6, 2021 · 25 comments · Fixed by #776
Closed
13 tasks

Support for custom elements #123

vasicvuk opened this issue Jul 6, 2021 · 25 comments · Fixed by #776
Assignees
Labels
enhancement New feature or request form field Request for a new form field.

Comments

@vasicvuk
Copy link

vasicvuk commented Jul 6, 2021

Is your feature request related to a problem? Please describe.

As a developer, it would be beneficial to have the ability to add custom elements to the palette in both the designer and viewer interfaces. Incorporating an upload component with custom behavior would be an example use case.

Describe the solution you'd like

Ideally, a library consumer should be able to override the element registry, and hook their own element types into it. We would then expect to be able to define the structure required for the properties panel (and eventually a schema for component validation) as part of our new component implementation.

Describe alternatives you've considered

n/a


This is a pretty large scale refactoring, and it requires quite a few steps:

  • Allow overriding the element registry
  • Hook our palette into the element registry
    • Use the registry as a source instead of statically defining components, or create a modularized registry source
    • Handle icons, which as of now are statically defined.
  • Extract the properties panel configurations into the components somehow (the biggest problem)
  • Create a plan to gather feedback around the extension points
  • Communicate how to create custom form elements
    • Cf. How we do this in bpmn land via examples
    • E.g. Add guide in our docs (especially a reference of FormField.config options)

Cf. the recent spike we conducted

Image
Image

@vasicvuk vasicvuk added the enhancement New feature or request label Jul 6, 2021
@nikku
Copy link
Member

nikku commented Jul 13, 2021

Thanks for opening this issue. Custom elements are on our mid-term roadmap.

@nikku nikku added the backlog Queued in backlog label Jul 13, 2021
@VictorAITERA
Copy link

Hi, is there a way to develop our own component and add it to the left panel (FORM ELEMENTS LIBRARY) ?

dfulgham added a commit to dfulgham/form-js that referenced this issue Mar 20, 2022
Work in progress, proof of concept only.

Ref bpmn-io#123
@dfulgham
Copy link
Contributor

dfulgham commented Mar 20, 2022

@nikku - I have a working prototype of custom elements (although very brutal and needs a lot of refactoring and test specs), let me know if this is what you envisioned.

added customFields and customPropertyPanelGroups options. And then added my File Input Field Type and Property Panels to the FormEditor and From options in the Playground.js I could have chose a simpler example, but I had this one done already.

https://github.com/dfulgham/form-js/tree/custom-elements-API

image

The Following two Objects make up a custom field type and property panels.

FileInput Custom Field Type:

const customFields = [{
  icon: () => {
    return (<svg id="file-icon" width="54px" height="54px" viewBox="0 0 54 54" xmlns="http://www.w3.org/2000/svg">
      <g transform="matrix(0.440697, 0, 0, 0.288703, -72.000198, -53.362686)" style="">
        <path d="M 261.968 260.403 C 260.414 260.403 258.876 260.629 257.367 261.077 C 256.679 232.991 241.391 
        ...
        316.711 285.911 296.698 C 285.911 276.683 275.17 260.403 261.968 260.403 Z" stroke="none" fill="#000002" fill- 
       rule="nonzero" />
      </g>
    </svg>);
  },

  label: 'File',
  type: 'FileInput',
  keyed: true,
  emptyValue: '',
  propertyPanelGroups: ['FileGeneral','FileProperties', 'validation'],
  create: (options = {}) => options,
  fieldRenderer: (props, FieldComponents, Form, Utils) => {
    const type = 'FileInput';

    const { formFieldClasses, prefixId } = Utils;
    const { Label, Description, Errors } = FieldComponents;

    const { disabled = false, field, value = '' } = props;

    const { description, id, label, validate = {} } = field;

    const { required } = validate;

    // eslint-disable-next-line react-hooks/rules-of-hooks
    const [errors, setError] = useState([]);


    const convertBase64 = (file) => {
      return new Promise((resolve, reject) => {
        const fileReader = new FileReader();
        fileReader.readAsDataURL(file);

        fileReader.onload = () => {
          resolve(fileReader.result);
        };

        fileReader.onerror = (error) => {
          reject(error);
        };
      });
    };

    const onChange = async ({ target }) => {
      const base64Value = await convertBase64(target.files[0]);
      props.onChange({
        field,
        value: base64Value
      });
      setError(_validate(base64Value));
    };

    const _validate = (value) => {
      const { allowedMimeTypes } = field;
      if (!allowedMimeTypes || allowedMimeTypes.length === 0) { return []; }
      const _allowed = allowedMimeTypes.split(',');
      const fileMime = value.split('data:')[1].split(';')[0];
      if (allowedMimeTypes) {
        return _allowed.includes(fileMime) ? [] : [`File type not allowed, must be one of: ${allowedMimeTypes}`];
      }
    };

    const onReset = () => {
      props.onChange({
        field,
        value: ''
      });
    };

    const onClick = () => {
      let newWindow = window.open('');
      newWindow.document.write(
        "<iframe width='100%' height='100%' src='" +
              value + "'></iframe>"
      );
    };

    const { formId } = Form._getState();

    if (disabled === true && value)
      return <div class={ formFieldClasses(type, errors) }>
        <Label id={ prefixId(id, formId) } label={ label } required={ required } />
        <a onClick={ onClick }><button type="secondary" class="fjs-button">View/Download</button></a>
        <Description description={ description } />
      </div>;

    return (
      <div class={ formFieldClasses(type, errors) }>
        <Label id={ prefixId(id, formId) } label={ label } required={ required } />

        <input
          class="fjs-input"
          disabled={ disabled }
          id={ prefixId(id, formId) }
          onInput={ onChange }
          onReset={ onReset }
          type="file"
          value={ value }
        />
        <Description description={ description } />
        <Errors errors={ errors } />
      </div>

    );
  }
}];

Custom Property Panels:

const customPropertyPanelGroups = [
  {
    name: 'FileGeneral',
    groupRenderer: (field, editField, Components, MinDash) => {
      const { Group, LabelEntry, DescriptionEntry,KeyEntry } = Components;
      const entries = [];
      entries.push(<LabelEntry editField={ editField } field={ field } />);

      entries.push(<DescriptionEntry editField={ editField } field={ field } />);

      entries.push(<KeyEntry editField={ editField } field={ field } />);

      return (
        <Group label="General">
          {
            entries.length ? entries : null
          }
        </Group>
      );
    }

  },
  {
    name: 'FileProperties',
    groupRenderer: (field, editField, Components, MinDash) => {
      const { id } = field;
      const { get } = MinDash;
      const { Group,TextInput } = Components;
      const path =['allowedMimeTypes'];

      // custom logic

      const onInput = (value) => {
        if (editField && path) {
          editField(field, path, value);
        }
      };

      return (
        <Group label="File Validation">
          <div class="fjs-properties-panel-entry">
            <TextInput
              id={ `${id}-allowedMimeTypes` }
              label="Allowed Mime Types"
              onInput={ onInput }
              value={ get(field,path) }
            />
            <div class="fjs-properties-panel-description">Comma separated list of file types. e.g. 'image/png,application/pdf'</div>
          </div>

        </Group>
      );
    }
  },];

@dfulgham
Copy link
Contributor

Created a Rating input as well, pretty straight forward and functional

image

@nikku
Copy link
Member

nikku commented Mar 22, 2022

@dfulgham This is what I've envisioned.

Will check it out in detail in the next days.

But from what it looks like (quick overview) this is a great start towards a custom element API.

@dfulgham
Copy link
Contributor

@nikku going to need a little help in getting the tests created and code coverage. Is there a way in the local environment to generate code coverage? I'm pretty new to lerna

@nikku nikku added the form field Request for a new form field. label Apr 27, 2022
@nikku nikku self-assigned this May 10, 2022
@nikku nikku removed their assignment Jun 8, 2022
@SeanAda
Copy link

SeanAda commented Jan 26, 2023

Is there any plan, when this feature will be implemented?

@vsgoulart
Copy link
Contributor

Is there any plan, when this feature will be implemented?

Hey @SeanAda, this is in our roadmap but still, we have no set date for implementing this

@dfulgham
Copy link
Contributor

@nikku is there any progress on this implementation?

I see we are at 1.0 now, if it would be helpful, if I can get some information on the planned approach I can look at doing a draft implementation.

I'm needing this for our self hosted Camunda Enterprise application.

@pinussilvestrus
Copy link
Contributor

pinussilvestrus commented Jul 17, 2023

@dfulgham Unfortunately, there are no concrete steps planned right now. Personally, I'd like to tackle this topic sooner than later, but it's always a matter of priority in comparison to other topics we tackle.

As form-js reached a good state in the recent month, I can imagine this topic to be a reasonable next step to open up for for our community 🚀

We are open - and really appreciate - all suggestions you'd contribute 🙂 👍

cc @christian-konrad @marcosgvieira

@dfulgham
Copy link
Contributor

@pinussilvestrus Since I would eventually like to be able to use any custom components in the Web Modeler (self hosted) and potentially the Cloud Modeler (?) as we have implemented Camunda Enterprise. It won't be as easy as my previous attempt while using the form-js in a web project.

Is there any good starting points relating to the requirements from Camunda Forms modeller? Obviously a simple JSON object (like that used for custom diagram elements in bpmn-js) can't hold the functional pieces required for forms.

@pinussilvestrus
Copy link
Contributor

pinussilvestrus commented Jul 20, 2023

@dfulgham Good news: we discussed this topic internally and agreed this as a priority for this quarter (Q3/2023). To manage expectations we do not aim to deliver something end-to-end by the end of this, but at least a concept of how we want to tackle this, also especially given the requirements we have to make it work across our product stack (Web Modeler, Tasklist, ...).

Since I would eventually like to be able to use any custom components in the Web Modeler (self-hosted) and potentially the Cloud Modeler (?) as we have implemented Camunda Enterprise. It won't be as easy as my previous attempt while using the form-js in a web project.

Indeed this is a challenge. For self-managed, I can see this rather easily, but for SaaS, we need indeed an answer on how to provide some kind of plugins - not only for forms potentially but also for BPMN. What I have loose in mind is to follow a similar approach as we do with Connectors in BPMN land where such "customizations" are first-class citizen in the product, with an own editor to develop such.

Is there any good starting points relating to the requirements from Camunda Forms modeller? Obviously a simple JSON object (like that used for custom diagram elements in bpmn-js) can't hold the functional pieces required for forms.

I agree a JSON schema is insufficient here, cf. also the list of requirements above. It includes rendering parts and ways of how to deliver extensions to other components, such as the palette and properties panel. But, this is nothing we don't already solve in other libraries, as in bpmn-js.

To summarize, as we tackle this one conceptionally, any input (as suggestions, feedback, or PRs) here is highly welcome 👍

@pinussilvestrus
Copy link
Contributor

We created a spike of needed changes to support everything we listed above (and more).

#776

We will discuss internally how to proceed and how to bring this out of the door.

@pinussilvestrus pinussilvestrus added in progress Currently worked on and removed backlog Queued in backlog labels Sep 12, 2023
@pinussilvestrus
Copy link
Contributor

pinussilvestrus commented Sep 12, 2023

Update after HTO team weekly

  • We will continue on the spike and making sure the shown extension points are getting out of the door.
  • Furthermore, we want to gather feedback around the extension points before we support it in Modeler + Tasklist, as this comes with major security considerations
    • One idea was to have a sandbox environment of the Form Editor where one can upload and try out custom form field extensions, e.g. via Form Playground demo, Desktop Modeler plugin or something what we already delivered in the hackdays
    • Another idea mentioned was to persist the extension in a stringified form inside the form schema and handle it in Tasklist as well. This follows to security problems as well.

@DK-2013
Copy link
Contributor

DK-2013 commented Sep 13, 2023

While the functionality of adding custom components has not been implemented, is it possible to implement filtering so that only registered field types are displayed on Pallete, and not all default ones are imported?
For example, several types are customized, and the rest are not used - now all the default ones are rendered on Palette.

@pinussilvestrus
Copy link
Contributor

@DK-2013 I think this is not yet possible, at least not easily. The palette entries are generated from the FormFields component in a pretty static manner.

With the custom elements support that will land in soon it will generate the entries dynamically from the form fields service, so it will be possible to override the form fields definition registry and with that also what is being displayed in the palette.

You can follow the spike above. We will get back to this issue and notify once he feature is released.

pinussilvestrus pushed a commit that referenced this issue Sep 14, 2023
pinussilvestrus pushed a commit that referenced this issue Sep 14, 2023
pinussilvestrus pushed a commit that referenced this issue Sep 14, 2023
@dfulgham
Copy link
Contributor

@pinussilvestrus This is great, I will be playing with this on the weekend when I find some spare time. Really needing this for our app. We currently parse our form controls into two groups, one for formJS supported controls and another for ones we tag with a custom property "customElement":true. The formJS we render to the left and custom controls we render to the right with custom form implementation. Not really ideal, so it will be great to be able to render the custom controls with formJS in the location where they need to be.
image

@pinussilvestrus
Copy link
Contributor

@dfulgham thanks for sharing ! Any early feedback is more than welcome 👍

@pinussilvestrus
Copy link
Contributor

In the form playground demo we created a simple demo to upload custom form components (WIP): camunda/form-playground#106 (comment)

Please refer to the notes about some important implementation constraints to make extensions work in the demo environment.

pinussilvestrus pushed a commit that referenced this issue Sep 19, 2023
pinussilvestrus pushed a commit that referenced this issue Oct 5, 2023
@bpmn-io-tasks bpmn-io-tasks bot added needs review Review pending and removed in progress Currently worked on labels Oct 5, 2023
pinussilvestrus pushed a commit that referenced this issue Oct 12, 2023
pinussilvestrus pushed a commit that referenced this issue Oct 17, 2023
pinussilvestrus pushed a commit that referenced this issue Oct 19, 2023
pinussilvestrus pushed a commit that referenced this issue Oct 20, 2023
pinussilvestrus pushed a commit that referenced this issue Oct 20, 2023
@bpmn-io-tasks bpmn-io-tasks bot removed the needs review Review pending label Oct 20, 2023
@pinussilvestrus
Copy link
Contributor

A good bunch of improvements in our extensions API was just merged via #776 🎉

This will be released with the next minor. We already contributed a bpmn-io style example via bpmn-io/form-js-examples#7.

Next steps

  • Spread the word via examples, blog post(s) and other ways
  • Wait for feedback to make extensions even smoother
  • We'll have a team internal workshop next week to envision the next steps naming bringing custom forms into the Camunda stack

@nikku
Copy link
Member

nikku commented Oct 20, 2023

Spread the word via examples, blog post(s) and other ways

Would be great to cover this (major feature!) with a dedicated blog on bpmn.io. If possible we want to consider to teaser it via our social channels, too.

@pinussilvestrus
Copy link
Contributor

I created an issue and will tackle this in the next iteration after our HTO offsite 👍

form-js v1.4.0 will get two blog posts, which is great :)

@Ken-Scofield
Copy link

Can i create custom components with form-editor or playground mode (v.1.8.3), and how do that? Please help me! thanks @nikku @pinussilvestrus

@christian-konrad
Copy link
Contributor

@Ken-Scofield
Copy link

Hi @Ken-Scofield , it's best to read this docs page and the surrounding pages: https://docs.camunda.io/docs/next/apis-tools/frontend-development/forms/customize-and-extend/custom-components/

@christian-konrad thanks, boss! Can i use custom components in 'FormEditor' not 'Playground'?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request form field Request for a new form field.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants