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

Question: Creating a completely immutable Embed/BlockEmbed #1337

Closed
mcmire opened this issue Feb 28, 2017 · 5 comments
Closed

Question: Creating a completely immutable Embed/BlockEmbed #1337

mcmire opened this issue Feb 28, 2017 · 5 comments

Comments

@mcmire
Copy link

mcmire commented Feb 28, 2017

So here is what I am trying to do.

I would like to embed a form in the editor. The use case in my app is that users are able to build a page by adding text and images, and in addition, they are also able to create a form (in a sort of modal, which is separate) and then add that to the page as well.

The route I have gone so far is to create a FormBlot by subclassing BlockEmbed. This almost works, but there are two issues I am facing:

  1. The user is able to cursor into the form. Ideally, if the cursor is right before the form, pressing the right or down arrow key should skip completely past the form (and a similar behavior should occur if the cursor is right after the form and left or up is pressed).
  2. Placing the cursor inside text that is inside the form (such as that inside a label or input tag) and pressing keys changes the form, when it should not.

So, has anyone figured out a way to accomplish both of these things? I have tried rooting around in the Quill and Parchment codebases and have not had much luck so far.

@mcmire
Copy link
Author

mcmire commented Mar 3, 2017

Just a followup: It is technically possible to do this, but it gets really complicated, and this is mainly due to contenteditable. I'll document some of the stuff I did though in case anyone finds this useful:

  1. I mentioned before that I made a new blot class to represent the form (FormBlot). What I didn't mention before is that:
    • I added a method to this called index that always returns 0 (this will come in handy later).
    • I added two methods: select and unselect. These methods will be called when the blot is selected (either by clicking on it or using the cursor) or unselected (only by using the cursor).
    • In select, I dispatched a custom event on this.domNode (the element that represents the blot) called select-block-embed. I made it bubble upward, and set a blot property on the Event object to this (the FormBlot instance). In this method I also set an active class so that I could draw a box around the form.
    • In unselect, I took away the active class.
    • In the constructor, I added a handler to listen to mousedown so that select is called.
  2. Then I vendored the Quill code and modified some of it. I changed the event handlers at the top of core/selection.js so that update is no longer fired on keyup, mouseup, or touchend. Instead of this, I added another handler to call update when selectionchange occurred on the editor. Since you can't technically listen for selectionchange on anything other than the entire document, inside of the handler I had to check that document.activeElement (the node where the selection was created or changed) was inside of, or was itself, this.root (which is the editor). After this, I then modified update so that at the top, after it calls this.getRange() to get the current selection state, but before setting this.lastRange and then comparing it to the previous selection state, it fires a new event called selection-before-change.
  3. Now that that was in place, in my own code, I could then listen for selection-before-change. In the handler, I took the range that was passed in and then used Quill#getLine to get the current blot.
    • If this blot was a FormBlot, then:
      1. I called select on the FormBlot and then called quill.selection.setRange(null, "silent") to hide the cursor (otherwise it appears inside of the form). (The "silent" is to prevent update from getting called again, which is going to cause issues since we're still in the middle of update.)
      2. I cached the FormBlot and range.index in a variable that is defined outside of the selection-before-change handler.
      3. Finally, I also attached an event handler to the editor to listen to keydown, because at this point we need to tell the editor that if the user presses up, down, left, or right the blot needs to get unselected and the cursor needs to reappear either above or below the form. I accomplished this by either calling quill.setSelection(index - 1, 0, "silent") or quill.setSelection(index + 1, 0, "silent") depending on the direction. (Here, index is the range.index that we cached before in the variable I mentioned before.)
    • If the blot that corresponds to the current selection is not a FormBlot, then I called unselect on the FormBlot that I cached before, cleared out this cache, and removed the keydown handler from the editor.
  4. Finally, I also listened for select-block-embed to occur on the editor element. (As mentioned before, FormBlot will emit this event when the blot is selected, and the blot can be selected two ways: when the selection is changed via the cursor and when the element is clicked on.) In the handler I made sure that we were caching the selected blot in the variable I mentioned before and that the selection was cleared (using quill.selection.setRange(null, "silent")).

So this logic got me pretty far: I could click on the form to select it, and I could use the arrow keys to cursor around, and depending on where it was the form might get selected and unselected automatically.

The problem arose when I wanted to implement dragging and dropping for the form. It just does not seem that contenteditable was not built for embedding HTML and treating that HTML as an indivisible, immutable unit. I started going down the route of implementing my own drag and drop logic, and I quickly realized that it was completely buggy. And I think it's all because we are working within a contenteditable.

I think if you wanted to do this sort of thing right, you'd have to get rid of contenteditable completely and implement it from scratch using plain JavaScript just like Google Docs does (cursor, selections, dragging and dropping, everything). It's a real shame and I wish there were better support for this in the browser.

@mcmire mcmire closed this as completed Mar 3, 2017
@alexkrolick
Copy link
Contributor

Having tried something like this a few times, ultimately it's easier to create a meta-layout that contains a series of different node types (rich text/form/gallery) than to try to hook into the editor directly. You can still make it appear as one document canvas visually.

@laurensiusadi
Copy link

@alexkrolick I'm making something similar. How do I make that 'meta-layout' thing? Using an iframe?

Can I get a demo on this?

@alexkrolick
Copy link
Contributor

alexkrolick commented Jun 8, 2018

No iframe, just something like this:

----------------
    toolbar
----------------
 some rich text
     editor
----------------
    picture
----------------
 some rich text
     editor
       ...

And manually manage the navigation/focus of the different types of nodes.

@l3ernardo
Copy link

@mcmire I´m trying to embed a form in the quill editor. Seems like you´ve done similar maybe you can help me

After reading over the documentation I created an formBlot by also subclassing BlockEmbed.

// formBlock.js file:

import * as Quill from 'quill';

const formEmbed = Quill.import('blots/block/embed');

export class CustomBlock extends BlockEmbed {
/* stuck here creating the form fields, just looking for few radio buttons that would set an pre-define template on the editor based on the choose, */
}

CustomBlock.blotName = 'custom';
CustomBlock.tagName = 'form';

Would like to know how you set the fields for your form, in case you also have done it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants