Laravel livewire and ckeditor - ckeditor

I'm trying to integrate ckeditro with laravel livewire but everytime I enter content into the editor, livewire removes the editor from my textarea.
My code is as follows
<div class="form-group" wire:ignore>
<label class="required" for="description">Page Description</label>
<textarea class="form-control ckeditor" id="description" name="description" cols="30" rows="10"
wire:model="description"
></textarea>
</div>
The following is the javascript
$(document).ready(function () {
CKEDITOR.instances['description'].on('change', function(e){
#this.set('description', e.editor.getData());
});
});
Any help would be appreciated
Thanks
Update
I am slowly getting there. The only issue I have is that when I save the form, the ckeditor is removed from the textarea.
<div class="form-group" wire:ignore>
<label class="required" for="description">Page Description</label>
<textarea class="form-control ckeditor" id="description" name="description" cols="30" rows="10"
wire:model.debounce.9999999ms="description"
x-data
x-init="
CKEDITOR.instances['description'].on('change', function(e){
$dispatch('input', e.editor.getData())
});
"></textarea>
</div>

How I did solved CKEDITOR problem in Laravel-Livewire.
Textarea
<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>

Livewire has a .defer modifier that batches data updates with the next network request. More on Livewire Official Documentation
Tested on Laravel 8.4
Livewire v2.0
<form wire:submit.prevent="submit">
<div wire:ignore>
<textarea wire:model.defer="description" class="form-control" id="description" name="description">{!! $description !!}</textarea>
</div>
</form
Your CK editor instance
<script>
ClassicEditor
.create(document.querySelector('#description'))
.then(editor => {
editor.model.document.on('change:data', () => {
#this.set('description', editor.getData());
})
})
.catch(error => {
console.error(error);
});
</script>

I had the same problem.
You can re-initialize ckeditor with 'dehydrate'.
public function dehydrate() {
$this->emit('initializeCkEditor');
}
And in the parent view you can initialize the CKEditor again
Livewire.on('initializeCkEditor', function () {
ClassicEditor.create(document.getElementById('idOfTextarea')).then(editor => { thisEditor = editor });
});

When you save the form, Livewire reloads the Livewire component. Currently, you are loading ckeditor when the doc is ready.
On your save event, emit an event like:
$this->emitUp('postAdded');
And then in your javascript, add a listener for the event like:
<script>
window.livewire.on('postAdded' => {
CKEDITOR.instances['description'].on('change', function(e){
#this.set('description', e.editor.getData());
});
});
</script>
This should load ckeditor on the newly updated component code.
(https://laravel-livewire.com/docs/events)
Also, I would recommend changing this:
wire:model.debounce.9999999ms="description"
to this:
wire:model.lazy="description"
as this will wait until the textarea loses focus for Livewire to update.
(https://laravel-livewire.com/docs/properties#lazy-updating)

use wire:ignore in your container. This will prevent livewire to re-rendered the component. The explanation is here, just search in the code.
https://laravel-livewire.com/docs/2.x/alpine-js#creating-a-datepicker
<div id="where-the-livewire-id-attached">
<div id="container-wrapper" wire:ignore>
<textarea id="your-editor" cols="30" rows="10"></textarea>
<div id="word-count"></div>
</div>
</div>

I did this using alpinejs x-init initialize the editor wire:model binds data value from the editor wire:ignore is for letting know livewire to skip the editor from re-rendering when the state changes
<div class="row">
<div class="col mb-3">
<label for="productDescription" class="form-label">Product
Description</label>
<div wire:ignore>
<textarea x-data x-init="ClassicEditor
.create(document.querySelector('#description'), {
})
.then(editor => {
editor.model.document.on('change:data', () => {
#this.set('description', editor.getData());
})
})
.catch(err => {
console.error(err.stack);
});" id="description" wire:model.lazy="description" name="description" required>
{!! $description !!}
</textarea>
</div>
#error('description')
<span
class="text-warning"><small><strong>{{ $message }}</strong></small></span>
#enderror
<div class="form-text"><small class="text-muted">Enter a detailed
description of the product</small></div>
</div>
</div>

To make it work with ckeditor here is how we can do it
ClassicEditor
.create( document.querySelector( '#yourCkId' ) )
.then( editor => {
console.log( editor );
editor.model.document.on( 'change:data', () => {
// Below #this.set breaks down my ckeditor so I am avoiding it to set ckeditor value
// #this.set("description", editor.getData());
//instead use this
$('#yourTextAreaId').val(editor.getData());
} );
} )
.catch( error => {
console.error( error );
} );
and the HTML
<form wire:submit.prevent="store(document.querySelector('#yourTextAreaId').value)">
<div wire:ignore>
<div id="yourCkId">{!! $description !!}</div>
</div>
<textarea id="yourTextAreaId" wire:key="uniqueKey" wire:model="description" style="display: none">
{!! $description !!}
</textarea>
</form>
And Your Livewire Controller
public function store($note){
$this->note = $note;
}

<script src="https://cdn.ckeditor.com/ckeditor5/30.0.0/classic/ckeditor.js"></script>
<div>
<div wire:ignore>
<textarea class="form-control" wire:model="description" x-data x-init="
ClassicEditor
.create( $refs.editorDescription)
.then(function(editor){
editor.model.document.on('change:data', () => {
#this.set('description', editor.getData())
})
})
.catch( error => {
console.error( error );
} );" x-ref="editordescription">
</textarea>
</div>

Related

Using Vue, Inertia, Laravel, how do I update a file object in an edit component?

I currently have an edit page, which hits an update function to update the Itinerary object when changed.
On this page, if I click submit straight away, and dd() the $request from the ItineraryController, it returns all of the existing form data, as expected.
If I edit the data in the fields, then submit it, it returns successfully with a full request object as expected.
If, however, I choose a file in the "replace file" selector, the entire request object shows as null when the form is submitted, and thus can't be submitted.
How can I adjust this so that the "replace file" input is operational, and fills the request object with the existing itinerary data?
Component:
<template>
<form #submit.prevent="submit">
<div class="row w-75 m-auto">
<h1>Edit Itinerary</h1>
<div class="col-md-6">
<label for="title">Title</label>
<input v-model="form.title" class="form-control" name="title" placeholder="Itinerary Title" type="text" />
</div>
</div>
<div class="row w-75 m-auto">
<div class="col-md-6">
<label for="gen_narrative">Narrative</label>
<textarea v-model="form.gen_narrative" class="form-control" name="gen_narrative" placeholder="Itinerary Narrative"></textarea>
</div>
</div>
<div class="row w-75 m-auto">
<div class="col-md-6">
<label>Current Photo</label>
<img :src="assetUrl(props.itinerary.f_photo)" alt="featured photo" />
</div>
</div>
<br />
<div class="row w-75 m-auto">
<div class="col-md-6">
<label for="f_photo">Replace Photo</label>
<input class="form-control" name="f_photo" type="file" #input="fileChange" />
</div>
</div>
<div class="row w-75 m-auto">
<div class="col-md-6">
<label for="authorid">Author Name</label>
<input v-model="form.authorid" class="form-control" name="authorid" placeholder="Author Name" type="text" />
</div>
</div>
<div class="row w-75 m-auto">
<div class="col-md-6">
<button class="btn btn-primary" type="submit">Edit Itinerary</button>
</div>
</div>
</form>
</template>
<script setup>
import { useForm } from "#inertiajs/inertia-vue3";
function assetUrl(path) {
return process.env.MIX_BASE_URL + "storage/" + path;
}
function fileChange(event) {
form.f_photo = event.target.files[0];
}
let props = defineProps({
itinerary: {
type: Object,
required: true,
},
});
let form = useForm({
title: props.itinerary.title,
gen_narrative: props.itinerary.gen_narrative,
f_photo: null,
authorid: props.itinerary.authorid,
stops: props.itinerary.stops,
});
let submit = () => {
console.log(form.title);
form.patch("/itineraries/" + props.itinerary.id);
form.reset();
};
console.log(form);
</script>
<style scoped></style>
Controller:
public function edit($id)
{
$itinerary = Itinerary::find($id);
return Inertia::render('Itineraries/Edit', [
'itinerary' => $itinerary,
]);
}
public function update(Request $request, $id)
{
$itinerary = Itinerary::find($id);
$itinerary->title = $request->title;
$itinerary->gen_narrative = $request->gen_narrative;
//upload the photo
if ($request->hasFile('f_photo')) {
$itinerary->f_photo = $request->f_photo->store('public/itinerary_photos');
}
$itinerary->authorid = $request->authorid;
$itinerary->save();
return redirect('/itineraries');
}
let form = useForm({
forceFormData: true,
title: props.itinerary.title,
gen_narrative: props.itinerary.gen_narrative,
f_photo: null,
authorid: props.itinerary.authorid,
stops: props.itinerary.stops
})
The best I can come up with is writing your file upload inline
<div class="row w-75 m-auto">
<div class="col-md-6">
<label for="f_photo">Replace Photo</label>
<input class="form-control" name="f_photo" type="file" #input="form.f_photo = event.target.files[0]"/>
</div>
</div>
Based on this example in official documentation you can do
<input type="file" #input="form.f_photo = $event.target.files[0]" />
Your issue is probably because Inertia does not natively support uploading files using a multipart/form-data request for the put, patch or delete methods, as is stated here (in the "Multipart limitations" section).
An alternative way is to submit without form helper using post method with the _method attribute of 'put', like:
Inertia.post("/itineraries/" + props.itinerary.id", {
_method: 'put',
f_photo: form.f_photo,
})

I am using websocket to send images in a chat with spring boot and STOMP client with js

Hello everyone I am using websocket to send images using stomp client and am facing this error:
Uncaught TypeError: Cannot read property 'files' of null
at sendMyImage (app.js:46)
at HTMLButtonElement.<anonymous> (app.js:68)
at HTMLButtonElement.dispatch (jquery.js:5201)
at HTMLButtonElement.q.handle (jquery.js:5009)
sendMyImage # app.js:46
(anonymous) # app.js:68
dispatch # jquery.js:5201
q.handle # jquery.js:5009
This is the code that am using in my index.html
<html>
<head>
<title>chat app</title>
<link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="/main.css" rel="stylesheet">
<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/sockjs-client/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/stomp.min.js"></script>
<script src="/app.js"></script>
</head>
<body>
<div id="main-content" class="container">
<div class="row">
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="connect">WebSocket connection:</label>
<button id="connect" class="btn btn-default" type="submit">Connect</button>
<button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
</button>
</div>
</form>
</div>
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="participantId">ParticipantId :</label>
<input type="number" id="participantId" class="form-control" placeholder="Your id here...">
</div>
<div class="form-group">
<label for="id">Content :</label>
<input type="text" id="content" class="form-control" placeholder="Your message content here...">
</div>
<button id="send" class="btn btn-default" type="submit">Send</button>
</form>
</div>
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label>Select an image and hit send:</label>
<input type="file" id="file" accept="image/*"/>
<button id="sendImage" class="btn btn-default" type="submit">Send Image</button>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-md-12">
<table id="conversation" class="table table-striped">
<thead>
<tr>
<th>chatmessages</th>
</tr>
</thead>
<tbody id="chatmessages">
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
and this is my app.js code :
var stompClient = null;
const messageWindow = document.getElementById("messages");
const fileInput = document.getElementById("file");
const sendImageButton = document.getElementById("sendImage");
function setConnected(connected) {
$("#connect").prop("disabled", connected);
$("#disconnect").prop("disabled", !connected);
if (connected) {
$("#conversation").show();
}
else {
$("#conversation").hide();
}
$("#chatmessages").html("");
}
function connect() {
var socket = new SockJS('/ws');
socket.binaryType = "arraybuffer";//heere
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/user/chatmessages', function (chatmessage) {
showChatMessage(JSON.parse(chatmessage.body));
});
});
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendParticipantIdAndContent() {
stompClient.send("/app/chat", {}, JSON.stringify({'participantId': $("#participantId").val(),'content': $("#content").val()}));
}
function sendMyImage() {
let file = fileInput.files[0];
sendMessage(file);
fileInput.value = null;
}
function showChatMessage(message) {
$("#chatmessages").append("<tr><td>" + message.message + "</td></tr>");
}
function addImageToWindow(image) {
let url = URL.createObjectURL(new Blob([image]));
messageWindow.innerHTML += `<img src="${url}"/>`
}
$(function () {
$("form").on('submit', function (e) {
e.preventDefault();
});
$( "#connect" ).click(function() { connect(); });
$( "#disconnect" ).click(function() { disconnect(); });
$( "#send" ).click(function() { sendParticipantIdAndContent(); });
$( "#sendImage" ).click(function() { sendMyImage(); });
});
I really want to know what is the problem because i didn't get why he would be reading the file as a null.
Please tell me where did i mess up
and thank you
The problem is because the fileInput in your javascript is read as null.
you could try something like this
<input type="file" id="file" >
and access content like this
let file = document.getElementById("file").files[0];
And the problem here is definitely not related stomp or spring or websocket.

Laravel Vuejs form builder

In my project i have some events, each with a number of tags.
These tags are defined by the administrator user.
Some of these tags may have parameters.
For example, an email tag has two "Sender" and "Receiver" parameters.
Or the transfer tag has 2 parameters "From" and "To". and etc.
Do I have to use the form builder?
How do I implement this using Laravel and Vuejs?
Use Vue JS Component in Laravel like this.
Create Contact.vue component inside resources\assets\js\components then place your Vuejs there.
<template>
<div>
<h1>Contacts</h1>
<form action="#" #submit.prevent="createContact()">
<div class="form-group">
<label>Name</label>
<input v-model="contact.name" type="text" name="name" class="form-control">
</div>
<div class="form-group">
<label>Email</label>
<input v-model="contact.email" type="text" name="email" class="form-control">
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">New Contact</button>
</div>
</form>
</div>
</template>
<script>
export default {
data: function(){
return {
contact:{
name:'',
email:'',
}
}
},
methods: {
createContact: function(){
//call axios to submit the form values in your Laravel controller method
let self = this
axios.post('/contact/store', params)
.then(function(){
self.contact.name = '';
self.contact.email = '';
})
.catch(function(error){
console.log(error);
});
console.log(this.contact);
return;
}
}
}
</script>
In app.js file inside \resources\assets\js add the component
Vue.component('contacts', require('./components/Contacts.vue'));
Finally, call the contacts component inside your blade file.
<div class="container">
<div id="app">
<contacts></contacts>
</div>
</div>

Select2 form input not change value with vue

i need help. So basicly, when i used ordinary select form, i have succeed pass the data. but when i change to select2. Why my select2 not change value like select2 form??
This is with my select form ordinary with success pass data
<template>
<div class="col-md-3 col-sm-4">
<div class="filter-sidebar">
<div class="col-md-12 form-title">
<h2>Find the OPD</h2>
</div>
<form id="search" role="form" class="" #submit.stop.prevent="searchOpdForm" method="post">
<div class="col-md-12 form-group category">
<label class="control-label" for="category">Instansi / OPD</label>
<select id="opd" name="opd" class="form-control" v-model="selectopd.opd">
<option v-for="opd in opds" :key="opd.index" :value="opd.id">{{opd.nama_opd}}</option>
</select>
</div>
<div class="col-md-12 form-group button">
<button type="submit" class="btn tp-btn-primary tp-btn-lg btn-block">Cari</button>
<router-link :to="{name: 'listings'}" class="btn btn-reset"><i class="fa fa-repeat"></i>Reset</router-link>
</div>
</form>
</div>
</div>
</template>
<script>
import axios from 'axios'
import {API_BASE} from '../Config/config'
export default {
name: 'Sidebar',
data() {
return {
opds: [],
selectopd: {}
}
},
created() {
this.fetchOpds();
},
methods: {
searchOpdForm() {
let urlpost = `${API_BASE}/listings/search/opd`
console.log('submiting')
axios.post(urlpost, this.selectopd)
.then(response => {
this.$router.push({name: 'searchlistingopd', params: {listings: response.data.listings}})
console.log('successful')
})
.catch(error => {
console.log(error)
})
},
fetchOpds() {
let url = `${API_BASE}/get-opds`
axios.get(url)
.then(response => {
this.opds = response.data.opds
})
.catch(error => {
console.log(error)
})
}
}
}
</script>
And this is with example selected id's :
And if i changes to this, and a bit search with select2 form format, and my code is like this
<template>
<div class="col-md-3 col-sm-4">
<div class="filter-sidebar">
<div class="col-md-12 form-title">
<h2>Find the OPD</h2>
</div>
<form id="search" role="form" class="" #submit.stop.prevent="searchOpdForm" method="post">
<div class="col-md-12 form-group category">
<label class="control-label" for="category">Instansi / OPD</label>
<select id="opd" name="opd" class="form-control" v-model="selectopd.opd">
<option v-for="opd in opds" :key="opd.index" :value="opd.id">{{opd.nama_opd}}</option>
</select>
</div>
<div class="col-md-12 form-group button">
<button type="submit" class="btn tp-btn-primary tp-btn-lg btn-block">Cari</button>
<router-link :to="{name: 'listings'}" class="btn btn-reset"><i class="fa fa-repeat"></i>Reset</router-link>
</div>
</form>
</div>
</div>
</template>
<script>
import axios from 'axios'
import {API_BASE} from '../Config/config'
import jQuery from 'jquery'
let $ = jQuery
require('select2')
export default {
name: 'Sidebar',
data() {
return {
opds: [],
selectopd: {}
}
},
created() {
this.fetchOpds();
},
mounted() {
$("#opd").select2()
},
methods: {
searchOpdForm() {
let urlpost = `${API_BASE}/listings/search/opd`
console.log('submiting')
axios.post(urlpost, this.selectopd)
.then(response => {
this.$router.push({name: 'searchlistingopd', params: {listings: response.data.listings}})
console.log('successful')
})
.catch(error => {
console.log(error)
})
},
fetchOpds() {
let url = `${API_BASE}/get-opds`
axios.get(url)
.then(response => {
this.opds = response.data.opds
})
.catch(error => {
console.log(error)
})
}
}
}
</script>
Now when i choose value, it doesn't change anything. Just like this
Am I doing wrong with select2 code to use it in my component? Or i missing something to add like native jquery in ordinary?
You could change
<select id="opd" name="opd" class="form-control" v-model="selectopd.opd">
for
<select id="opd" name="opd" class="form-control" v-model="selectopd">

Vuejs components not getting displayed

I've a password_reset.vue script as below:
<template>
<form>
<div class="alert alert-danger" v-if="form.errors.has('photo')">
#{{ form.errors.get('photo') }}
</div>
<div class="row">
<div class="col-md-3">
<div class="label">Current Password</div>
</div>
<div class="col-md-9">
<div class="field--wrapper">
<label for="current_password">Current Password</label>
<input class="nom-fields" type="password"
v-model="form.current_password"
id="current_password" name="current_password"
placeholder="Current Password" value="">
</div>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div class="label">Repeat Password</div>
</div>
<div class="col-md-9">
<div class="field--wrapper">
<label for="password_repeat">Repeat Password</label>
<input class="nom-fields" type="password"
v-model="form.password_repeat"
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-9 text-right">
<button id="change_password"
class="blue-btn" v-on:click="update">
Change Password
</button>
</div>
</div>
</form>
</template>
<script>
export default{
data(){
return{
form: new MyForm({
current_password: '',
new_password: '',
password_repeat: '',
message: ''
})
};
},
methods: {
/**
* Update the user's password.
*/
update(e) {
e.preventDefault();
this.form.startProcessing();
this.$http.post('/account/change_password', this.form)
.then(function(response) {
this.form.finishProcessing();
})
.catch(function(response) {
this.form.setErrors(response.data);
});
},
}
}
</script>
and in my bootstrap.js file, I've:
import './interceptors.js';
import './forms/form.js';
import './forms/errors.js';
import PasswordReset from './settings/password_reset.vue'
new Vue({
el: 'body',
components: {
PasswordReset
},
ready() {
}
});
and in the app.js file I've:
import './bootstrap.js'
HTML:
<body>
<password-reset></password-reset>
</body>
But it seems the component is not getting displayed. I tried to alert a msg in the ready method and it's working fine. I'm loading Vue.js also.

Resources