Reading https://startutorial.com/view/using-ckeditor-with-laravel-livewire article I added ckeditor in Laravel 11 app in 1 page just as on example and it works ok.
But to have the editor some default text I remade it as :
<div class="flex flex-col space-y-10">
<div wire:ignore>
<textarea wire:model="message"
class="min-h-fit h-48 "
name="message"
id="message">{{ $message }}</textarea>
</div>
<div>
<span class="text-lg">You typed:</span>
<div class="w-full min-h-fit h-48 border border-gray-200">{{ $message }}</div>
</div>
</div>
To put $message inside of textarea block was a decision for me - maybe not best.
So I have an editor with ckeditor and default text, but when I start enter text inside of ckeditor area - ckeditor itself is lost and I see plain textarea and in "You typed" block I see newly text.
I have a component editor:
namespace App\Livewire\Admin;
use App\Enums\AppColorEnum;
use App\Enums\ConfigValueEnum;
use App\Enums\DispatchAlertTypeEnum;
use App\Library\ImageProps;
use App\Library\Traits\AppCommonTrait;
use App\Livewire\Forms\NewsCategoryForm;
use App\Models\NewsCategory;
use App\Rules\NewsCategoryRules;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\On;
use Livewire\Component;
use Livewire\WithFileUploads;
use NewsCategoryActiveEnum;
class NewsCategoryEditor extends Component
{
use AppCommonTrait;
use WithFileUploads;
public string $pageTitle = 'News category editor';
public ?int $id = null;
public bool $isEdit = false;
public NewsCategoryForm $form;
public array $imageProps = [];
public array $activeSelectionItems = [];
public array $appColorSelectionItems = [];
public function render()
{
return view('livewire.admin.news-category-editor')->layout('components.layouts.admin');
}
public function mount(?int $id = null, ?string $defaultAction = null)
{
...
if(!blank($id)) {
$this->edit($id);
}
}
public function edit($id)
{
$this->isEdit = true;
try {
$newsCategory = NewsCategory::getById($id)->firstOrFail();
} catch (ModelNotFoundException $e) {
self::dispatchAlert(DispatchAlertTypeEnum::ERROR,'Error reading news category: ' . $e->getMessage(), $this->pageTitle);
}
$this->form->setNewsCategory($newsCategory, $this->isEdit);
}
public function update(Request $request)
{
...
} // public function update()
public function cancel()
{
$this->redirect(route('admin.newsCategories.index'), navigate: true);
}
}
I use form app/Livewire/Forms/NewsCategoryForm.php :
namespace App\Livewire\Forms;
use App\Models\NewsCategory;
use Livewire\Form;
class NewsCategoryForm extends Form
{
public NewsCategory $newsCategory;
public $id;
public $name;
public $description;
...
public bool $isEdit = false;
public function setNewsCategory(NewsCategory $newsCategory, bool $isEdit)
{
$this->newsCategory = $newsCategory;
$this->name = $newsCategory->name;
$this->description = $newsCategory->description;
...
$this->isEdit = $isEdit;
}
public function getNewsCategory(): NewsCategory
{
$this->newsCategory->name = $this->name;
$this->newsCategory->description = $this->description;
...
return $this->newsCategory;
}
public function setDescription(string $description)
{
$this->description = $description;
}
}
and blade template :
<div class="editor_form_wrapper">
<form class="form-editor" @if($form->isEdit) wire:submit="update"
@else wire:submit="store" @endif enctype="multipart/form-data">
@include('admin.validation_errors', ['show_error_items' => true])
@if($form->isEdit)
<div class="editor_field_block_wrapper">
<div class="editor_field_block_device_splitter">
<div class="w-4/12 pb-0 pl-2 md:pt-3">
<label for="id" class="editor_field_block_device_label">Id: </label>
</div>
<div class="p-2 w-full">
<input id="id" name="id" type="text" class="editor_form_readonly" value="{{ $form->id }}"
tabindex="-1" readonly/>
</div>
</div>
</div>
@endif
<div class="editor_field_block_wrapper">
<div class="editor_field_block_device_splitter">
<div class="w-4/12 pb-0 pl-2 md:pt-3 ">
<label for="name" class="editor_field_block_device_label">Name: <span
class="editor_form_aria_required" aria-required="true"> * </span></label>
</div>
<div class="p-2 w-full">
<input id="name" type="text"
class="editor_form_input"
maxlength="50"
wire:model="form.name"
autocomplete="off"
placeholder="Fill string describing news category"
tabindex="10"
autofocus/>
@error('form.name')
<span class="error_text">{{$message}}</span>
@enderror
</div>
</div>
</div>
<div class="editor_field_block_wrapper">
<div class="editor_field_block_device_splitter">
<div class="w-4/12 pb-0 pl-2 md:pt-3">
<label for="description" class="editor_field_block_device_label">Description: <span
class="editor_form_aria_required" aria-required="true"> * </span> </label>
</div>
<div class="px-2 w-full">
<textarea wire:model="form.description"
class="min-h-fit h-48 " rows="4" cols="80"
name="description"
id="description">{{ $form->description }}</textarea>
<div>
<span class="text-lg">You typed:</span>
<div class="w-full min-h-fit h-48 border border-gray-200">{{ $form->description }}</div>
</div>
</div>
@error('form.description')
<span class="error_text">{{$message}}</span>
@enderror
</div>
</div>
...
@if($form->isEdit)
<div class="editor_field_block_wrapper">
<div class="editor_field_block_device_splitter">
<div class="w-4/12 pb-0 pl-2 md:pt-3 ">
<label for="created_at" class="editor_field_block_device_label">Created at</label>
</div>
<div class="p-2 w-full">
<input
id="created_at" name="created_at" type="text"
class="editor_form_readonly"
value="{{ $form->created_at }}"
tabindex="-1"
readonly/>
</div>
</div>
</div>
@if(!blank($form->updated_at))
<div class="editor_field_block_wrapper">
<div class="editor_field_block_device_splitter">
<div class="w-4/12 pb-0 pl-2 md:pt-3 ">
<label for="updated_at" class="editor_field_block_device_label">Updated at</label>
</div>
<div class="p-2 w-full">
<input
id="updated_at" name="updated_at" type="text"
class="editor_form_readonly"
value="{{ $form->updated_at }}"
tabindex="-1"
readonly/>
</div>
</div>
</div>
@endif
@endif
<div class="flex items-center justify-end p-2 text-right sm:px-6">
<div class="mb-3 flex justify-between">
<button class="btn_editor_form_cancel" wire:click="cancel">
Cancel
</button>
<button class="btn_editor_form_submit" type="submit">
Edit
</button>
</div>
</div>
</form>
</div>
<script>
ClassicEditor
.create(document.querySelector('#description'))
.then(editor => {
editor.model.document.on('change:data', () => {
console.log('editor.getData()::')
console.log(editor.getData())
@this.set('form.description', editor.getData());
})
})
.catch(error => {
console.error(error);
});
</script>
Any ideas what is wrong in my code and how to fix it ?
It seems that in the view you forgot to enclose the <textarea> with a wire:ignore as you showed in the initial code snippet, so when Livewire updates the DOM also rewrites the textarea and ClassicEditor loses its references.
BUT in this particular view, for a reason I haven't been able to figure out yet, there is an element that causes problems. It is:
input id="id" name="id" type="text" class="editor_form_readonly" value="{{ $form-> id }}" tabindex="-1" readonly />
Trying to rename id and name (e.g. id="_id" name="_id"
) everything seems to work fine. Keep in mind however that not having a wire:model the input would also be ignored by Livewire. The only thing I can think of is that Livewire has a mechanism that prevents the id column of a model from being changed but I'm not sure if that's relevant to this case...
A suggestion: since @this.set() makes an ajax call everytime you type a character, I recommend changing it like this:
editor.model.document.on('change:data', () => {
@this.form.description = editor.getData(); // <~~~ THIS
})
in this way the changes are kept locally and they can be sent to the backend when the form is submitted.
If you want, for debugging purposes, a button that performs the update can be added to the view:
<button type="button" wire:click="$refresh">
Refresh
</button>
Also, since you printed the initial value of the textarea in the HTML, you could remove wire:model="form.description"
from it, because this property is handled by @this.form.description = editor.getData()