Help with recognizing checkboxes

Hello,

I’m still trying to learn about ProseMirror and the various components that fit together. One of the things I’ve been trying to get working is to add a checkbox in a document.

I’m using the dino example as a starting point. Not sure what parts I’m doing incorrectly, but I believe I created:

  • An input rule that recognizes “[]”, which I want to be converted into a checkbox.
  • A menu option under the “Insert” section for a checkbox node.
  • A “checkbox” node, and added it as a possible node under a “document”'s content.

I’ve defined a checkbox nodespec like so:

checkbox: {
      defining: true,
      attrs: {"checked": {default: ""}},
      parseDOM: [{tag: 'input', attrs: {"type": "checkbox", "checked": "", "class": "myCheckbox"}}],
      toDOM: node => ["input", {"type": "checkbox", "class": "myCheckbox", "checked": node.attrs.checked}] 
}

For the Insert Menu item, I’ve written it like so:

let addCheckbox = function(state, dispatch) {
  let {$from} = state.selection, index = $from.index()
    if (!$from.parent.canReplaceWith(index, index, orgSchema.nodes.checkbox)) {
      console.log("Can't insert checkbox")
      return false
    }
    if (dispatch) {
      dispatch(state.tr.replaceSelectionWith(orgSchema.nodes.checkbox.create()))
      console.log("Created checkbox")
    }
    return true
}

let menu = buildMenuItems(schema)
menu.insertMenu.content.push(new MenuItem({
  title: "Insert Checkbox",
  label: "Checkbox",
  enable(state) { return true }, // TODO: Figure out how this works...
  run: addCheckbox
}))

Currently, whenever I try inserting via the menu, I just get the log message that I can’t insert a checkbox. I create a checkbox in straight HTML, but when Prosemirror interprets the document, the checkbox seems to be removed.

My initial thought is that I’m still defining the schema incorrectly… since the checkbox doesn’t appear on load. I’m not exactly sure what’s going incorrect about it. Any suggestions as to what I need to look into?


Here is a fuller code example of what I’ve been doing: https://glitch.com/edit/#!/obtainable-steel

(Sorry if it’s kind of messy)

Thanks for the help!

You’re going to have to read the guide a bit more, I think. The thing where you take index() from a position and then try to use that as a document-level position is very confused (the index into a local node isn’t a position, nor does it apply to the document). Also, the way you want to use typing (inside a textblock) to insert these, yet your schema defines them as occurring at the top level (which isn’t a textblock) suggests you don’t a clear model of where these go in the document.

I think I am getting confused on the details. :fearful: Sorry if I’m totally misunderstanding what you’re saying.

The way I wrote addCheckbox(state, dispatch) is very similar to how it is described to write insertDino(type) in the dino example. From what I can tell, they both use index() in the same manner… unless I’m mis-interpreting something.

$from is coming from the state.selection, which I understand it as a ResolvedPos, which holds infomation like the position in the document? Why am I not able to use index() in the same manner in this case?


You are probably right. I want checkboxes to be inserted at the beginnings of just about any line, so I thought it should go in the top level. The only time where checkboxes should NOT be inserted is in front of other checkboxes.

When you are referring to "textblock"s, are you talking about nodes that can contain a “text” node?

Oh, you’re right, I misread your code, sorry. Probably the problem is that the selection is going to be in an inline position (text selections always are), and your schema doesn’t allow checkboxes there.

Yes. If you want these to be able to appear at the start of, for example, paragraphs, you might want to change the content expression for paragraphs to something like "checkbox? inline*". Having them at the top level, which isn’t a textblock, makes it impossible to interact with them in text. Or you must do some position manipulation when [] is typed and find the nearest top-level position (but I still think this won’t be what you want—the checkboxes will appear as separate block elements).

You should also look into your parseDOM property. "type" doesn’t go in attrs, and neither does class. You’re matching every kind of input node, and you don’t read the checked property from the DOM. Probalby something like this is closer to what you want:

[{tag: "input[type=checkbox].myCheckbox", getAttrs: node => ({checked: node.checked})}]
2 Likes

With the NodeSpec

    checkbox: {
      defining: true,
      attrs: {"checked": {default: ""}},
      parseDOM: [{tag: "input[type=checkbox].myCheckbox", getAttrs: node => ({checked: node.checked})}],
      toDOM: node => ["input", {"type": "checkbox", "class": "myCheckbox", "checked": node.attrs.checked}]
    }

the checked in attrs is not reactive. It keeps the same value in the output of state.toJSON() after I toggle it in the editor view. How could I get the right value?

I suspect ProseMirror isn’t noticing the change because it listens to DOM changes, and toggling a checkbox doesn’t change the DOM. You’ll have to define a little node view that adds an event handler to create transactions when the checkbox is toggled. (Or, even more minimalist, you could register an editor-wide event handler with handleDOMEvents, then use posAtDOM to figure out where event.target is located in the document, and create a transaction.)

1 Like

I don’t get how to handle the “checked” attribute, which is a boolean attribute. Should we set the default value to false, null…? An empty string seems to make the checkbox always checked. Removing this attribute will lose the checked state on next reload.

Update: attrs: { checked: { default: undefined }} seems to work. I’ll try to setup the event as described above to detect when the input is checked.