How to initialize value of Codemirror Binding to yjs

My main problem is initializing the text/value of a code editor(CodeMirror) on my website without it affecting the way I save/send POST requests to my backend. The following is the pug code I use for the POST request:

p
  form(id='form' method='POST', action='/docs/edit/'+docs._id)
    textarea(name="doo" id="content" style="display: none;")=docs.content
    textarea(name="foo" id="editortext" style="display: none;")
    input.btn.btn-primary(type='submit' value='Save Doc')

What I’m trying to do here, is send docs.content to textarea with id “content” so that I can use that to initialize the value of my code editor and then put the content of whats in the code editor in the textarea “editortext” once I click the submit button. Thus, the POST request would fetch me the data from both textareas, where I can then save the content of the “editortext” textarea to my database. The logic of the code editor is referenced in the same pug file to a javascript file after rollup transpilation. The following is a chunk of the pre-compiled code:

/* eslint-env browser */

import * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'
import { CodeMirrorBinding } from 'y-codemirror'
import CodeMirror from 'codemirror'
import 'codemirror/mode/clike/clike.js'

window.addEventListener('load', () => {
  const ydoc = new Y.Doc()
  const provider = new WebsocketProvider(
    `${location.protocol === 'http:' ? 'ws:' : 'wss:'}${location.host}`,
    'codemirror',
    ydoc
  )
  const yText = ydoc.getText('codemirror')
  const editorContainer = document.createElement('div')
  editorContainer.setAttribute('id', 'editor')
  document.body.insertBefore(editorContainer, null)
  let content = document.getElementById("content").value
  const editor = CodeMirror(editorContainer, {
    mode: 'text/x-java',
    lineNumbers: true
  })

  editor.setValue(content)

  document.getElementById("form").onsubmit = function(evt){
    document.getElementById("editortext").value = editor.getValue();
  }

Most of this code is from the yjs-codemirror demo except for the declaration of the content variable,the invocation of the setValue method, and the document.getElementById(“form”) block. What this code currently does is send me the right information to my database. However, I am having trouble initializing the value of the code editor when I open up the document. The setValue method doesn’t work, neither does doing the following:

  const editor = CodeMirror(editorContainer, {
    value: content,
    mode: 'text/x-java',
    lineNumbers: true
  })

All of the prior examples fail even if I replace the content variable with some string. The only thing that seems to work is the following:

  const editor = CodeMirror(editorContainer, {
    mode: 'text/x-java',
    lineNumbers: true
  }).setValue(content)

However, the problem with this is that for some reason, I get the following errors in the console browser:

TypeError: codeMirror is undefined (y-codemirror.js:160:4)
TypeError: editor is undefined (index.js:28:10)

For reference, the javascript that I have been showing in this question was all from the index.js file. In any case, because the editor is undefined, I can no longer set the value of my “editortext” textarea to the CodeMirror Textarea and I can’t save what is written to the code editor to my database. I’m not sure as to why this would happen, I’m not sure if this is particular to the CodeMirrorBinding from yjs but any help on this would be massively appreciated.

Hi @notemaster,

I assume that you mean you are unable to set the value of the CodeMirror editor.

The CodeMirrorBinding binds the value of the Y.Text type to a CodeMirror instance. The setValue method works, but the value of the editor is overwritten by the binding:

ytext.insert(0, 'ytext')
const editor = CodeMirror(..)
editor.setValue('my value')
editor.value() // => "my value"
new CodeMirrorBinding(editor, ytext)
editor.value() // => "ytext value"

I suggest that you set the value after it has been bound to the YText type.

Another note: There is nothing like a default value in Yjs. Initially, the Yjs document is empty until it synced with the server. So you might want to wait until the client synced with the server before setting the value.

const setDefaultVal = () => {
  if (ytext.toString() === '') {
    ytext.insert(0, 'my default value')
  }
}
if (provider.synced) {
  setDefaultVal()
} else {
  provider.once('synced', setDefaultVal)
}

2 Likes

I assume editor.setValue() returns undefined. This is why the binding won’t work and you can set the initial value of the editor.

1 Like

Thank you so much, this looks like it worked! Sorry, still a beginner to all of this!

Hey,

I’ve started experimenting with Yjs (in combination with quilljs). It looks pretty awesome provides a nice API for that complex topic.

I was also wondering about a default value. I am trying to store the document in a central database and allow users to come back to it later. I am wondering if you already have some best practices for this?

@notemaster does the initialization work for you? how often do you save your document? I am worried that the document might change and a new user might get an invalid (old) state?

This is my current implementation to load and store documents from a postgres database:
https://gist.github.com/bumi/c7ead5db27cc6c48ba1869f8696ed278 - maybe it’s helpful for somebody, and feedback is welcome! :slight_smile:

2 Likes

Thanks for sharing :slight_smile:

Hi @bumi, is it still working? I’m making a test with your example but when the applyUpdate occurs, the clients no longer sync more.

I am not sure. I haven’t updated to the latest version yet - mainly because I had some issues when using it with plain JS (not typescript, or modules or whatever)

It is running on:
“y-protocols”: “^0.2.0”,
“y-quill”: “^0.0.2”,
“y-websocket”: “^1.2.5”,
“yjs”: “^13.0.4”

1 Like