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

Use text editor in component #48

Closed
trankhacvinh opened this issue Jul 19, 2024 · 6 comments
Closed

Use text editor in component #48

trankhacvinh opened this issue Jul 19, 2024 · 6 comments

Comments

@trankhacvinh
Copy link

Initialize CKEditor (or another editor) in a component view with JavaScript. It works initially, but when any hydro-bound input changes, the component re-renders and the editor is lost.
How can I use the editor with a component?

@kjeske
Copy link
Contributor

kjeske commented Jul 24, 2024

Hi! Since CKEditor is controlled outside Hydro by their JS logic, I would create a separate component for it and use inside your form. Inside your CKEditor component, which only has one textarea, I would not use binding, but an action. Could you show how you initialize your CKEditor, so I can suggest how it could work?

@trankhacvinh
Copy link
Author

Hi! Since CKEditor is controlled outside Hydro by their JS logic, I would create a separate component for it and use inside your form. Inside your CKEditor component, which only has one textarea, I would not use binding, but an action. Could you show how you initialize your CKEditor, so I can suggest how it could work?

Yes, of course. Here is how I do it, please correct me or suggest me how to do it right. Thanks.

I am creating a FormComponent using HydroComponent. It includes Title and Content fields, which work well with hydro-bind. To integrate CKEditor (or TinyMCE) for the Content field, I initialized it on a separate textarea named ContentEditor using JavaScript. I sync data between ContentEditor and Content through events. However, changing data in another input, such as the Title, causes the CKEditor instance to disappear.

@kjeske
Copy link
Contributor

kjeske commented Aug 2, 2024

It seems like CKEditor v5 became a bit more complex in therms of setting up. But here is what I got. I suppose you have something similar when it comes to invoking CKEditor (using CDNs for testing) either in your Layout or some common place:

<script type="importmap">
{
    "imports": {
      "ckeditor5": "https://cdn.ckeditor.com/ckeditor5/42.0.2/ckeditor5.js",
      "ckeditor5/": "https://cdn.ckeditor.com/ckeditor5/42.0.2/"
    }
}
</script>

<script type="module">
    import { ClassicEditor, Essentials, Paragraph} from 'ckeditor5';
  
  window.initCKEditor = (element) => {
    ClassicEditor.create(element, {
      plugins: [ Essentials, Paragraph ],
      toolbar: ['undo', 'redo']
    }).then(editor => {
      editor.model.document.on('change:data', () => {
        const data = editor.getData();

        const customEvent = new CustomEvent('contentchange', {
          detail: { data }
        });

        element.dispatchEvent(customEvent);
      });
    })
  };
</script>

In the code above I create initCKEditor so I can use it alter in my Hydro component. It emits contentchange once the document changes.

Then here is my CkEditor component:

CkEditor.cshtml

@using static Hydro.Param
@model CkEditor

<div>
  <div
    x-init="setTimeout(() => initCKEditor($el), 100); @DateTime.Now.Ticks"
    hydro-on:contentchange.debounce.200ms="@(() => Model.Update(JS("$event.detail.data")))">

    @Html.Raw(Model.Value)
  
  </div>
</div>

CkEditor.cshtml.cs

using Hydro;

namespace HydroDemo.Pages.TextEditor;

public class CkEditor : HydroComponent
{
    [Transient]
    public string Value { get; set; }
    
    [SkipOutput]
    public void Update(string value) =>
        Dispatch(new CkEditorChanged(value));
}

public record CkEditorChanged(string Html);
  • Value is Transient since we don't want to send this state back from the client on every action. It will be received and processed only on content change via Update action.
  • On Update we use SkipOutput, because we just want to inform the parent component without rerendering.

Usage:

TextEditorExample.cshtml

@model TextEditorExample

<div>
  <hydro name="CkEditor" params="@(new { Value = Model.HtmlContent })" />
</div>

TextEditorExample.cshtml.cs

using Hydro;

namespace HydroDemo.Pages.TextEditor;

public class TextEditorExample : HydroComponent
{
    public string HtmlContent { get; set; }

    public override void Mount()
    {
        HtmlContent = "<p>Hello there.</p><p>Second paragraph.</p>";
    }

    public BasicTextEditorExample()
    {
        Subscribe<CkEditorChanged>(Handle);
    }

    private void Handle(CkEditorChanged data)
    {
        HtmlContent = data.Html;
    }
}

@kjeske
Copy link
Contributor

kjeske commented Aug 2, 2024

I see a need to dispatch events also on the containing component as a JS event that you can handle on the parent. I will think more about it. Then we would avoid some extra request while updating data.

@kjeske
Copy link
Contributor

kjeske commented Aug 3, 2024

Closing this and opening #73. You still can use the method I posted above in the meantime. The fix will be just an improvement, which will make the solution faster.

@kjeske kjeske closed this as completed Aug 3, 2024
@trankhacvinh
Copy link
Author

trankhacvinh commented Aug 5, 2024 via email

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

2 participants