Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

mohansharma's avatar

How to bind CKEditor value to Laravel livewire component

How to bind CKEditor value to Laravel livewire component. CKEditor is a type if WYSIWYG editor

I am initializing CKEditor like this in my wire view component

<div id="editor"></div>

but I can't use wire:model to bind its value to a public variable in livewire. Is there any way I could pass the editor content when form submitted?

0 likes
13 replies
bobbybouwmann's avatar

I don't think you can. CKEditor is Javascript that changes the DOM automatically. Livewire is rendered on the server and is updated by ajax calls, which are rendered on the server.

  • Livewire renders the initial component output with the page (like a Blade include), this way it's SEO friendly.
  • When an interaction occurs, Livewire makes an AJAX request to the server with the updated data.
  • The server re-renders the component and responds with the new HTML.
  • Livewire then intelligently mutates DOM according to the things that changed.

According to this, it's not possible as far as I know. You can try to ask Caleb on Twitter if the knows how to do this ;)

2 likes
kurucu's avatar

Just spent an evening getting this working! My code (using Alpine and Livewire together) is as follows:

                <div
                    class="form-textarea w-full"
                    x-data
                    x-init="
                        BalloonEditor.create($refs.myIdentifierHere)
                        .then( function(editor){
                            editor.model.document.on('change:data', () => {
                               $dispatch('input', editor.getData())
                            })
                        })
                        .catch( error => {
                            console.error( error );
                        } );
                    "
                    wire:ignore
                    wire:key="myIdentifierHere"
                    x-ref="myIdentifierHere"
                    wire:model.debounce.9999999ms="content"
                >{!! $content !!}</div>

What it does is as follows (largely following Livewire docs):

  • x-data initialises an Alpine component for the scope of the div.
  • x-init runs the code specified, basically gets BalloonEditor (choose the CKEditor of your preference) to init the div as a CKEditor. It takes $refs.myIdentifierHere as a magic argument - Alpine puts the correct javascript in for you.
  • myIdentifierHere is simply a cross-reference to x-ref="myIdentifierHere" below.
  • wire:ignore tells Livewire not to do the Dom diffing for this div (or its children)
  • wire:key makes it easier for Livewire to track the div... I'm not sure why it's needed yet, but it's buggy without. It's usually used for loops etc.
  • wire:model.debounce.99999ms is a hack too - the lazy modifier doesn't submit properly on every instance, so this is a workaround to using lazy. Otherwise it works the same way (near enough).
10 likes
stephenjude's avatar

@kurucu this was really helpful. I used wire:model.debounce.2000ms="content" and it works fine. Thanks for sharing :)

1 like
awizemann's avatar

Even though this is an old post, here is an update that might be worthwhile. First off, the code is slick and works like a charm. Thank you @kurucu, great work. If your struggling to have the editor clear when you are loading another element or have multiple elements, just change the wire:key="myIdentifierHere" to an incrementing value (mike the ID from your model).

2 likes
georgiarnaudov's avatar

Here's how I managed to deal with this situation.

Livewire view

<div class="form-textarea w-full" x-data="editorApp()" x-init="init($dispatch)" wire:ignore wire:key="ckEditor" x-ref="ckEditor" wire:model.debounce.9999999ms="content">{!! $content !!}</div>

Javascript

<script>
    /**
     * An alpinejs app that handles CKEditor's lifecycle
     */
    function editorApp() {
        return {
            /**
             * The function creates the editor and returns its instance
             * @param $dispatch Alpine's magic property
             */
            create: async function($dispatch) {
                // Create the editor with the x-ref
                const editor = await ClassicEditor.create(this.$refs.ckEditor);
                // Handle data updates
                editor.model.document.on('change:data', function() {
                    $dispatch('input', editor.getData())
                });
                // return the editor
                return editor;
            },
            /**
             * Initilizes the editor and creates a listener to recreate it after a rerender
             * @param $dispatch Alpine's magic property
             */
            init: async function($dispatch) {
                // Get an editor instance
                const editor = await this.create($dispatch);
                // Set the initial data
                editor.setData('{!! $content !!}')
                // Pass Alpine context to Livewire's
                const $this = this;
                // On reinit, destroy the old instance and create a new one
                Livewire.on('reinit', async function(e) {
                    editor.destroy();
                    await $this.create($dispatch);
                });
            }
        }
    }
</script>

Livewire component

public function submit()
    {
        // Process form data, save to the database, etc.
      
		// Emit the reinit event after you've done processing the form
        $this->emit('reinit');
    }

Hope it helps someone. :)

3 likes
NoTimeForCaution's avatar

Very nice! Thank you.

Works well, but the editor is not cleared following form submission.

Should the following clear the input contents:

editor.destroy()

Thanks again!

Neeraj1005's avatar

@mohansharma @bobbybouwmann THis is how I did managed the CKEditor on my app

This answer for CKEDITOR USERS

textarea field

<div wire:ignore class="form-group row">
    <x-label class="col-md-3 col-form-label" for="message" :value="__('Compose message')" />
    <div class="col-md-9">
        <textarea wire:model="message" class="form-control required" name="message" id="message"></textarea>
        <x-error-message :value="__('message')" />
    </div>
</div>

CKEDITOR-4

<script src="https://cdn.ckeditor.com/4.16.1/full/ckeditor.js"></script>
<script>
            const editor = CKEDITOR.replace('message');
            editor.on('change', function(event){
                console.log(event.editor.getData())
                @this.set('message', event.editor.getData());
            })
</script>

CKEDITOR-5

<script src="https://cdn.ckeditor.com/ckeditor5/27.1.0/classic/ckeditor.js"></script>
<script>
            ClassicEditor
                .create(document.querySelector('#message'))
                .then(editor => {
                    editor.model.document.on('change:data', () => {
					@this.set('message', editor.getData());
                    })
               })
                .catch(error => {
                    console.error(error);
                });
        </script>
7 likes
sevenTopo's avatar

@neeraj1005 Im using ckeditor 5 , and i followed what you did in your example , but i have a problem when i try to set the value of model message .

error message :

ckeditorerror.js:64 Uncaught TypeError: Cannot read property '$wire' of undefined

Adgower's avatar

I had similar issue using:

editor.setData('@this.content');

so I rendered the content inside the textarea on page load:

<textarea>{!! $content !!}</textarea>
1 like
binggle's avatar

Thanks for great work.

Actually I wish I have met your solution 1 year ago.

I have problem.

  1. the wire:ignore did not work.
<div wire:ignore >
    <textarea wire:ignore wire.model.lazy="content" ... /> 
</div>

If I put values on another input field, the ckeditor frame in textarea is disappeared.

The "wire:ignore" does not work.

So I added Wire Key

    <textarea wire:ignore wire:key="editor-{{ now() }}" wire.model.lazy="content" ... /> 

This time, the CkEditor frame was not disappeared. Good.

But this time, If I put value in input field, the textarea refresh automatically with keeping data.

It is just once being disappered and came back.

Textarea does not refresh any more with another putting value in second / thrid input field.

I would like to stop refreshing Textarea .

This is What I did.

make:livewire PostComponent 

in post-component.blade.php

<div  wire:ignore class="form-group">
    <input wire:model.lazy="title" />
</div>

<div wire:ignore class="form-group">
    @include('includes.editor-input')
</div>

in includes/editor-input.blade.php

<div wire:ignore >
    <label class="col-sm-2 control-label" for=" ">Content </label>
    <div class="col-sm-10" >
        <div class=" border border-gray-600 " >
            <textarea
                wire:ignore 
                wire:key="editor-{{ now() }}"
                class="w-full" wire:model.lazy="content" name="content" id="content"></textarea>
        </div>
        <div>
            @error('content') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
        </div>
    </div>

    <script src="https://cdn.ckeditor.com/4.16.1/full/ckeditor.js"></script>
    <script>
        const editor = CKEDITOR.replace('content', {
            height: 250
            , filebrowserUploadUrl: "{{ route( 'upload', ['_token'=> csrf_token() ] ) }}"
            , filebrowserUploadMethod: 'form'
        });
        editor.on('change', function(event) {
            @this.set('content', event.editor.getData());
        })

        CKEDITOR.config.allowedContent = true;
        CKEDITOR.filebrowserUploadMethod = 'form';
        CKEDITOR.editorConfig = function(config) {
            config.extraPlugins = 'colorbutton,colordialog,panelbutton';
        };

        CKEDITOR.on('dialogDefinition', function(ev) {
            var dialogName = ev.data.name;
            var dialogDefinition = ev.data.definition;

            if (dialogName == 'image') {
                var infoTab2 = dialogDefinition.getContents('advanced');
                dialogDefinition.removeContents('advanced');
                var infoTab = dialogDefinition.getContents('info');
                infoTab.remove('txtBorder');
                infoTab.remove('cmbAlign');
                infoTab.remove('txtWidth');
                infoTab.remove('txtHeight');
                infoTab.remove('txtCellSpace');
                infoTab.remove('txtCellPad');
                infoTab.remove('txtCaption');
                infoTab.remove('txtSummary');
            }
        });

    </script>
</div>

I struggled for whole day with this.

Can some guys explain why 'wire:ignore' not work and how to stop refreshing Textarea field.

1 like

Please or to participate in this conversation.