Building fluid forms with ProseMirror

Hi, thank you for the great work on ProseMirror.

Our team is using PM to build “fluid” forms for data entry. While our use case is different, here is a simplified example. Let’s say we want to build a contact form that has static fields (e.g. a contact must have exactly one name) and dynamic fields (e.g. a contact can have zero, one, or many phone numbers or email addresses).

Initially, the user is presented with a PM editor with only two nodes for the first and last names. When the user presses enter, an empty phone number node is added. The user can then input a number and press enter to add another phone number node, or can press enter on an empty phone number node to replace it with an empty email address node.

Rather than having many specific nodes (e.g. one for names, one for numbers, …), we want to have few generic nodes: a field node that contains text, a group node that contains field nodes, and a couple more. These generic nodes should have static and computed attributes that apply to all instance of a given node. For example, a field node could have a label set to “Email” and a valid getter that takes a node instance and says whether it’s a well formed email.

This approach would make it easier to define new fields (e.g. an address, a URL) and reorganize the structure of the form. In fact, we believe this could make for a great general-purpose package that could be used as an alternative to more traditional forms.

In general, what would be a scalable strategy to define a PM schema that could be used for such a purpose? And specifically, how can we extend a node specification for allowing static and computed attributes (we don’t want every node to carry its own instance of a label or a valid in its attributes).

We’d also appreciate any other input or advice on this idea in general. Thank you!

1 Like

I don’t really see through your use case well enough to give much advice here. label and valid don’t seem to fit node attributes well—those are additional data associated with an individual instance of the node, not the node type.

You’ll need to get very familiar with ProseMirror but you can totally do this. Something like:

const schema = new Schema({
  nodes: {
    text: {},
    label: {
      content: "text*",
      toDOM() { return ["label", 0] },
      parseDOM: [{tag: "label"}]
    },
    value: {
      content: "text*",
      toDOM() { return ["p", 0] },
      parseDOM: [{tag: "p"}]
    },
    formfield: {
      content: "label value",
      toDOM() { return ["div", 0] },
      parseDOM: [{tag: "div"}]
    },
    doc: {
      content: "formfield+"
    }
  }
})

(1) you’ll want to write a bunch of commands so that the UX feels right for moving around.

(2) if you want to validate form fields, then you can create custom formfields and use a NodeView to help with that.

In general, I think the best approach is going to be thinking about ProseMirror as a rendering engine – just a step above the DOM. Then parse the doc into whatever structured output that you want.

Good luck!