What is CKEditor 5?
CKEditor (formerly known as FCKeditor) is a WYSIWYG rich text editor which enables writing content directly inside of web pages or online applications. Its core code is written in JavaScript and it is developed by CKSource.
CKEditor is available under open source and commercial licenses.
Modern JavaScript rich text editor with a modular architecture. Its clean UI and features provide the perfect WYSIWYG UX for creating semantic content.
Features of CKEditor
- Written in ES6 with MVC architecture, custom data model, virtual DOM.
- Responsive images and media embeds (videos, tweets).
- Custom output format: HTML and Markdown support.
- Boost productivity with auto-formatting and collaboration.
- Extensible and customizable by design.
How to Use CKEditor in Laravel 9
First we will create a new Fresh Laravel Project. And then i will guide how to install and add image Adapter to upload image.
CKeditor provides own cloud service to upload image, But in this post we will learn how to create own own custom adapter to upload image. Here i am using CKeditor5.
i will add CDN for CKeditor.
<script src="https://cdn.ckeditor.com/ckeditor5/30.0.0/classic/ckeditor.js"></script>
And now create a form for submit the data to database.
<form class="forms-sample" action="/editor/post-save" method="POST"> @csrf <div class="form-group"> <label for="exampleInputUsername1">Post Title</label> <input type="text" name="title" class="form-control" id="exampleInputUsername1" placeholder="Post Title"> </div> <div class="row"> <div class="col-md-6"> <div class="form-group"> <label for="exampleInputEmail1">Main Category</label> <select class="form-control" name="category" id="category"> <option value="">Choose Main Category</option> @foreach($category as $v_category) <option value="{{ $v_category['id'] }}">{{ $v_category['title'] }}</option> @endforeach </select> </div> </div> <div class="col-md-6"> <div class="form-group"> <label for="exampleInputEmail1">Sub Category</label> <select class="form-control" name="sub_category" id="subcategory"> <option value="">Choose Sub Category</option> </select> </div> </div> </div> <div class="row"> <div class="col-md-12"> <div class="form-group"> <label for="exampleInputPassword1">Content</label> {{-- <div class="editor"></div> --}} <textarea name="content" id="editor" class="editor" rows="5"></textarea> </div> </div> </div> <div class="row"> <div class="col-md-12"> <div class="form-group"> <label for="exampleInputPassword1">Meta Data</label> {{-- <div class="editor"></div> --}} <textarea name="meta_data" class="form-control"></textarea> </div> </div> </div> <button type="submit" class="btn btn-primary mr-2">Submit</button> <button class="btn btn-light">Cancel</button> </form>
I will add the CKeditor in textarea that is
<textarea name="content" id="editor" class="editor"></textarea>
and at the bottom i will add a script for this textarea.
<script> ClassicEditor .create( document.querySelector( '#editor' ), { } ) .catch( error => { console.error( error ); } ); </script>
Now our CKeditor is working perfectly. But the problem is Image upload is not working. For this we must have to create a adapter.
<script> class MyUploadAdapter { constructor( loader ) { this.loader = loader; } upload() { return this.loader.file .then( file => new Promise( ( resolve, reject ) => { this._initRequest(); this._initListeners( resolve, reject, file ); this._sendRequest( file ); } ) ); } abort() { if ( this.xhr ) { this.xhr.abort(); } } _initRequest() { const xhr = this.xhr = new XMLHttpRequest(); xhr.open( 'POST', "{{route('image-upload', ['_token' => csrf_token() ])}}", true ); xhr.responseType = 'json'; } _initListeners( resolve, reject, file ) { const xhr = this.xhr; const loader = this.loader; const genericErrorText = `Couldn't upload file: ${ file.name }.`; xhr.addEventListener( 'error', () => reject( genericErrorText ) ); xhr.addEventListener( 'abort', () => reject() ); xhr.addEventListener( 'load', () => { const response = xhr.response; if ( !response || response.error ) { return reject( response && response.error ? response.error.message : genericErrorText ); } resolve( response ); } ); if ( xhr.upload ) { xhr.upload.addEventListener( 'progress', evt => { if ( evt.lengthComputable ) { loader.uploadTotal = evt.total; loader.uploaded = evt.loaded; } } ); } } _sendRequest( file ) { const data = new FormData(); data.append( 'upload', file ); this.xhr.send( data ); } } </script>
Now our final form buttom script will look like below code.
<script> class MyUploadAdapter { constructor( loader ) { this.loader = loader; } upload() { return this.loader.file .then( file => new Promise( ( resolve, reject ) => { this._initRequest(); this._initListeners( resolve, reject, file ); this._sendRequest( file ); } ) ); } abort() { if ( this.xhr ) { this.xhr.abort(); } } _initRequest() { const xhr = this.xhr = new XMLHttpRequest(); xhr.open( 'POST', "{{route('image-upload', ['_token' => csrf_token() ])}}", true ); xhr.responseType = 'json'; } _initListeners( resolve, reject, file ) { const xhr = this.xhr; const loader = this.loader; const genericErrorText = `Couldn't upload file: ${ file.name }.`; xhr.addEventListener( 'error', () => reject( genericErrorText ) ); xhr.addEventListener( 'abort', () => reject() ); xhr.addEventListener( 'load', () => { const response = xhr.response; if ( !response || response.error ) { return reject( response && response.error ? response.error.message : genericErrorText ); } resolve( response ); } ); if ( xhr.upload ) { xhr.upload.addEventListener( 'progress', evt => { if ( evt.lengthComputable ) { loader.uploadTotal = evt.total; loader.uploaded = evt.loaded; } } ); } } _sendRequest( file ) { const data = new FormData(); data.append( 'upload', file ); this.xhr.send( data ); } } function MyCustomUploadAdapterPlugin( editor ) { editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => { return new MyUploadAdapter( loader ); }; } ClassicEditor .create( document.querySelector( '#editor' ), { extraPlugins: [ MyCustomUploadAdapterPlugin ], } ) .catch( error => { console.error( error ); } ); </script>
Now create a route for image upload.
Route::post('save-image', [CommonController::class, 'image_save'])->name("image-upload");
And controller method, Add the below code in you controller , also please change controller name with your controller name in route file.
public function image_save(Request $request) { if($request->hasFile('upload')) { $filenamewithextension= $request->file('upload')->getClientOriginalName(); $filename = pathinfo($filenamewithextension, PATHINFO_FILENAME); $extension = $request->file('upload')->getClientOriginalExtension(); $filenametostore = $filename.'_'.time().'.'.$extension; $request->file('upload')->storeAs('public/uploads', $filenametostore); $request->file('upload')->storeAs('public/uploads/thumbnail', $filenametostore); $thumbnailpath = public_path('storage/uploads/thumbnail/'.$filenametostore); $img = Image::make($thumbnailpath)->resize(500, 150, function($constraint) { $constraint->aspectRatio(); }); $img->save($thumbnailpath); echo json_encode([ 'default' => asset('storage/uploads/'.$filenametostore), '500' => asset('storage/uploads/thumbnail/'.$filenametostore) ]); } }
I am using here image resize .
composer require intervention/image
and create an alias in config/app.php like below code.
'providers' => [ .... ..... Intervention\Image\ImageServiceProvider::class, ], 'aliases' => Facade::defaultAliases()->merge([ ..... 'Image' => Intervention\Image\Facades\Image::class, ])->toArray(),
and create symbolic link for storage access.
php artisan storage:link
Now your form are ready you can check, if its working fine please comment , if any problem then please comment i will help you sure.
Thanks
Happy Coding…
Some Popular Post
Summernote Editor With Image Upload in Laravel
Laravel 9 send mail using SMTP