I have chat message system.
I have code:
<template>
<li :class="className">
{{ message }}
</li>
</template>
<script>
export default {
props: [
'message',
'user',
'time',
'seen',
],
computed: {
className() {
return this.seen;
}
},
mounted() {
console.log('Component mounted.')
}
}
</script>
App.js:
data:{
message: '',
convId: 1,
chat: {
message: [],
user: [],
time: [],
seen: [],
},
typing: '',
},
....
watch: {
message() {
Echo.private('chat')
.whisper('typing', {
name: this.message
});
}
},
methods: {
send(){
if(this.message.length != 0 && this.message.length <= 4000) {
this.chat.message.push(this.message);
this.chat.user.push('you');
this.chat.time.push(this.getTime());
this.chat.seen.push('unread'). //set class unread message for user
axios.post('/sendMessage', {
message: this.message,
//lastName: 'Flintstone'
})
.then(response => {
console.log(response);
this.message = '';
})
.catch(error => {
console.log(error);
});
}
},
seenMessage() {
axios.post('/setMessagesSeen/' + this.convId) //this request mark messages in chat all readed for auhenticated user
.then( response => { this.chat.seen.push(''); //remove unread class })
.catch( response => { console.log(response) } )
},
getTime() {
let time = new Date();
return time.getHours() + ':' + time.getMinutes();
}
},
mounted() {
Echo.private('chat')
.listen('ChatEvent', (e) => {
this.chat.message.push(e.message);
this.chat.user.push(e.user);
this.chat.time.push(this.getTime());
this.chat.seen.push('unread'). //set class unread message for user
console.log(e);
})
.listenForWhisper('typing', (e) => {
if(e.name != '')
this.typing = 'typing..';
else
this.typing = null;
});
}
My chat.blade.php:
<message v-for="value,index in chat.message"
:key=value.index
:user=chat.user[index]
:message="chat.message[index]"
:time="chat.time[index]"
:seen="chat.seen[index]"
>
</message>
<div class="form-group">
<textarea maxlength="4000" cols="80" rows="3" class="message-input form-control" v-model='message' v-on:click="seenMessage"></textarea>
</div>
<div class="form-group">
<button type="button" class="btn btn-lg btn-primary" v-on:click="send">Send message</button>
</div>
My function seen:
public function setMessagesSeen(Conversation $conversation) {
$user = User::find(Auth::id());
$conversations = Chat::conversation($conversation->id);
//$dd = Chat::conversations($conversation)->for($user)->readAll();
dd(Chat::conversations($conversations)->for($user)->getMessages()->where('body', 'asdfsadfsd'));
//$message = Chat::messages($message)->for($user)->markRead();
broadcast(new HasSeenMessage($message));
return response('ok');
}
How I can send class "unread" to element div other user? I can paste class on current user, and I get color on element chat only for me, but how I can hide element for me and other user, when message is seen?
I want do read/unread function for users.
Example:
If user in real time send message I send class unread, when other user click on textarea, I remove class unread, and said user, that message is seen. How I can do it in real time add/remove class unread? My function is not working.
To do this you have to create an Event in your Laravel application that you will broadcast on a precise channel (you can for example give the name 'chat. {Conversation}. {User_id}') and with Laravel Echo you will listen this event!
I allowed myself to make some changes in your code -:)
I presume you have this class HasSeenEvent
<?php
namespace App\Events;
use App\Order;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class HasSeenEvent implements ShouldBroadcast
{
use SerializesModels;
public $message;
/**
* Create a new event instance.
*
* #param Message $message
* #return void
*/
public function __construct(Message $message)
{
$this->message = $message;
}
public function broadcastOn()
{
// I presume we can get conversation id like this : $this->message->conversation->id
return new PrivateChannel('chat.'.$this->message->conversation->id.'.'.$this->message->sender->id);
}
}
Then, in your routes/broadcast.php declare this route chat.{conversation}.{user_id}
In the function where you put the 'seen' to 1 you broadcast the event at the same time
broadcast(new HasSeenMessage($message))
Then you listen to this event in your vuejs code
components/Message.js
<template>
<li :class="className">
{{ message }}
</li>
</template>
<script>
export default {
props: [
'message',
'user',
'time',
'readed',
],
computed: {
className() {
return this.readed == 1 ? 'seen' : 'unread';
}
},
mounted() {
console.log('Component mounted.')
}
}
</script>
chat.blade.php
<message v-for="message,index in chat.messages"
:key="index"
:user="message.user"
:message="message.content"
:time="message.time"
:readed="message.readed"
>
</message>
<div class="form-group">
<button type="button" class="btn btn-lg btn-primary" v-on:click="send">Send message</button>
</div>
App.js
data: {
message: '',
convId: 1,
chat: {
messages: [],
/* message: [],
user: [],
time: [],
seen: [], */
},
typing: '',
},
....
watch: {
message() {
Echo.private('chat')
.whisper('typing', {
name: this.message
});
}
},
methods: {
send() {
if (this.message.length != 0 && this.message.length <= 4000) {
let data = {
content: this.message,
user: 'you',
time:this.getTime(),
readed: 0
}
this.chat.messages.push(data)
data = {}
axios.post('/sendMessage', {
message: this.message,
//lastName: 'Flintstone'
})
.then(response => {
console.log(response);
this.message = '';
})
.catch(error => {
console.log(error);
});
}
},
seenMessage() {
axios.post('/setMessagesSeen/' + this.convId) //this request mark messages in chat all readed for auhenticated user
.then(response => {
//This is not the best way to do that
this.chat.messages[this.messages.length -1 ].readed = 0
}).catch(response => {
console.log(response)
})
},
getTime() {
let time = new Date();
return time.getHours() + ':' + time.getMinutes();
}
},
mounted() {
Echo.private('chat')
.listen('ChatEvent', (e) => {
this.chat.messages.push({
content: e.message,
user: e.user,
time: this.getTime(),
readed: 0
})
console.log(e);
})
.listenForWhisper('typing', (e) => {
if (e.name != '')
this.typing = 'typing..';
else
this.typing = null;
});
// I presume to can access to user info
let that = this
Echo.private('chat.'+convId+'.'+user.id)
.listen('HasSeenMessage', (e) => {
let message = e.message
let lookingForMessage = that.chat.messages.find((m) => {
// I presume in your db messages table has field content and time
return m.content == message.content && m.time => message.time
})
try {
lookingForMessage.readed = 1
}catch (err){
// message not found
console.log(err)
}
})
}
Hope it helped you!
Related
Why do I get this error:
Error [vuex] Do not mutate vuex store state outside mutation handlers
This happens when I call this component.
<template lang="pug">
.auth-popup
button.auth-popup__button-top.auth-popup__button-top_close(
type='button'
aria-label='Закрыть форму авторизации'
#click='$emit(`close`)'
)
h2.auth-popup__title Вход или регистрация
form.auth-popup__form(#submit.prevent='submitHandler')
input.auth-popup__fake-input(v-show='showFakeInput' aria-hidden='true' autocomplete='off' ref='fakeInput')
label.auth-popup__label(for='authLogin') Телефон
input#authLogin.auth-popup__input(
type='tel'
autocomplete='tel'
v-model='login'
#input='inputHandler'
ref='loginInput'
)
p.auth-popup__error(v-if='login && !isPhoneAuth') Телефон указан неверно
p.auth-popup__error(v-if='error' v-html='error')
p.auth-popup__timer(v-if='getCodeTimer' v-html='codeTimerMessage')
button.auth-popup__button-send(
type='submit'
:disabled='!isLoginLength || !isPhoneAuth || getCodeTimer || showPreloader'
)
span.auth-popup__button-inner(v-if='!showPreloader') Получить код
Preloader.auth-popup__preloader(:show='showPreloader' :color='`#ffffff`')
button.auth-popup__button-email(
type='button'
#click='$emit(`email`)'
) Войти по почте
</template>
<script>
import { mapActions, mapMutations, mapGetters } from 'vuex'
import { REGEXPS } from '~/assets/js/utils/constants/regexps';
import { MESSAGES } from "~/assets/js/utils/constants/messages";
import delay from '~/assets/js/utils/functions/promiseTimeout';
import Preloader from "~/components/modules/Preloader"
export default {
name: 'AuthPhone',
components: {
Preloader
},
data() {
return {
showFakeInput: false,
showPreloader: false,
login: '',
error: ''
}
},
computed: {
isPhoneAuth() {
return REGEXPS.FULL_PHONE_SYMBOLS.test(this.login);
},
isLoginLength() {
const phoneDigits = this.login.trim().replace(/\D/g, ``);
return phoneDigits.length > 9;
},
createPhoneValue() {
let phoneNumber = this.login;
if (phoneNumber.startsWith('8')) {
phoneNumber = '7' + phoneNumber.slice(1);
}
return `+${phoneNumber.replace(/\D+/g, '')}`;
},
...mapGetters({
getAuthResponse: 'authorization/getAuthResponse',
getCodeTimer: 'authorization/getCodeTimer',
codeTimerMessage:'authorization/codeTimerMessage'
})
},
methods: {
...mapActions({
authRequest: 'authorization/authRequest'
}),
...mapMutations({
startCodeTimer: 'authorization/startCodeTimer',
resetCodeTimer: 'authorization/resetCodeTimer'
}),
inputHandler() {
this.error = '';
if (this.getCodeTimer) {
this.resetCodeTimer();
}
},
async submitHandler() {
this.showPreloader = true;
const sendData = {
ident_method: `PHONE`,
login: this.createPhoneValue
};
await this.authRequest(sendData)
.then(() => {
this.showPreloader = false;
const data = this.getAuthResponse;
if (data.result) {
if (data.is_registered && !data.is_active) {
this.error = MESSAGES.ERROR.ACCOUNT_DEACTIVATED;
} else if (data.is_code_sended) {
this.startCodeTimer(30);
this.$emit('enter');
}
} else if (MESSAGES.ERROR[data.error]) {
this.error = MESSAGES.ERROR[data.error];
} else {
this.error = data.error;
}
});
},
},
mounted() {
if (this.getAuthResponse.login && this.getAuthResponse.ident_method === `PHONE`) {
this.login = this.getAuthResponse.login;
}
this.showFakeInput = true;
this.$nextTick()
.then(() => {
this.$refs.fakeInput.focus();
return delay(500);
})
.then(() => {
this.$refs.loginInput.focus();
this.showFakeInput = false;
});
},
}
</script>
The problem arises in this mutation - this.startCodeTimer (30);
Mutation file:
export default {
setAuthResponse(state, data) {
state.authResponse = data
},
setCodeResponse(state, data) {
state.codeResponse = data
},
setRegResponse(state, data) {
state.regResponse = data
},
setAuthCode(state, data) {
state.authCode = data
},
startCodeTimer(state, time) {
state.newCodeTimer = time
state.codeTimerId = setInterval(() => {
if (state.newCodeTimer) {
state.newCodeTimer--
} else {
clearInterval(state.codeTimerId)
}
}, 1000)
},
resetCodeTimer(state) {
state.newCodeTimer = 0
}
}
If I understand correctly, then the problem is here.
state.codeTimerId = setInterval(() => {
if (state.newCodeTimer) {
state.newCodeTimer--
} else {
clearInterval(state.codeTimerId)
}
}, 1000)
But so far there are no ideas how to solve it.
The problem was that state cannot be changed inside setInterval.
Solution: Create a mutation that will change the state and call this mutation inside setInterval.
Example:
setNewCode(state, count) {
state.newCodeTimer = count
},
startCodeTimer(state, time) {
state.newCodeTimer = time
state.codeTimerId = setInterval(() => {
if (state.newCodeTimer) {
this.commit('authorization/setNewCode', time--)
} else {
clearInterval(state.codeTimerId)
}
}, 1000)
},
I have two components: Index and Create. Both of these components are loaded from different blade files. The problem is I cannot pass flash message as a prop since these are in different files. How to redirect after a submitted form has been created and and receive the flash message in component Index from Create?
public function store(Request $request)
{
Service::create([
'name' => $request->get('name'),
'vendor_id' => $request->get('vendor'),
'desc' => $request->get('desc'),
'hours' => $request->get('hours'),
'price_per_hour' => $request->get('price'),
'fixed_price' => $request->get('fixed_price'),
'user_id' => $request->user()->id
]);
if (\request()->wantsJson()) {
return response()->json([
'alert_store' => 'New service added successfully.'
]);
}
}
Create component:
if(ok) {
axios.get('/sanctum/csrf-cookie').then(response => {
axios.post('/api/services', {
name: this.form.name,
vendor: this.form.vendor,
desc: this.form.desc,
hours: this.form.hours,
price: this.form.price,
fixed_price: this.form.fixed_price,
})
.then(response => {
this.alert_store = response.data.alert_store
alert(this.alert_store)
window.location.href = '/admin/services';
})
.catch(function (error) {
console.log(error);
});
});
}
},
index.vue:
import Create from './Create.vue';
import Alert from '../Alert.vue';
export default {
props: ['alert_store'],
components: {
Create,
Alert,
},
data(){
return {
services: [],
alert_success: '',
alert_error: '',
errors: [],
success: [],
form: {
name: '',
desc: '',
hours: '',
price_per_hour: '',
vendor: '',
fixed_price: '',
},
selected: null,
}
},
mounted() {
this.loadServices()
this.getStatus()
},
methods: {
loadServices: function(){
axios.get('/api/getservicedata')
.then(response => {
this.services = response.data;
})
.catch(function(error){
console.log(error);
});
},
// getStatus(){
// axios
// .post('/api/services')
// .then(response => {
// console.log(response.data.alert_store)
// });
// },
isEditable(service)
{
this.form.name = service.name
this.form.desc = service.desc
this.form.hours = service.hours
this.form.price_per_hour = service.price_per_hour
this.form.vendor = service.vendor.id
this.form.fixed_price = service.fixed_price
if(service.isEditing)
{
this.selected = service.id
service.isEditable = false
}
this.$set(service, 'isEditing', true)
},
editService(service)
{
if(confirm('Are you sure?'))
{
axios.post(`/api/services/${service.id}`, {
_method: 'patch',
name: this.form.name,
vendor: this.form.vendor,
desc: this.form.desc,
hours: this.form.hours,
price_per_hour: this.form.price_per_hour,
vendor: this.form.vendor,
fixed_price: this.form.fixed_price
})
.then(response => {
this.alert_success = response.data.alert_update
this.success.push(Alert)
this.loadServices()
})
.catch(response => {
this.errors.push(Alert)
this.alert_error = `Could not edit, ${service.name}, from services!`
})
}
},
deleteService(service)
{
if(confirm('Are you sure?'))
{
axios.get('/sanctum/csrf-cookie').then(response => {
axios.delete(`/api/services/${service.id}`, {
__method: 'DELETE'
})
.then(response => {
this.alert_success = response.data.alert_delete
this.success.push(Alert)
this.loadServices()
})
.catch(response => {
this.errors.push(Alert)
this.alert_error = `Could not delete, ${service.name}, from services!`
})
});
}
}
}
}
You could pass props into index.vue component with different ways.
First and properly (on my mind) way is to use session data.
Like:
public function store(Request $request)
{
Service::create([
'name' => $request->get('name'),
'vendor_id' => $request->get('vendor'),
'desc' => $request->get('desc'),
'hours' => $request->get('hours'),
'price_per_hour' => $request->get('price'),
'fixed_price' => $request->get('fixed_price'),
'user_id' => $request->user()->id
]);
\request()->session()->put('yourawesomekey','value');
if (\request()->wantsJson()) {
return response()->json([
'alert_store' => 'New service added successfully.'
]);
}
}
And when you initialize your component in blade file:
...
<your-component-name alert_store="{{ session('yourawesomekey', 'default-value') }}" />
...
NOTE Don't forgot to cleanup session data \request()->session()->forget('yourawesomekey');
Hope it will work for you.
The issue is with the div tag with class log, I am trying to populate the text with data gotten from the api response. As I try to include the v-for directive ,the whole div disappears from the browser and there is no single error thrown by the console.
<template>
<div>
<div class="log" v-for="info in infos" v-bind:key="info" v-text="info.login">
Username
</div>
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
name: '',
infos: null,
}
},
methods: {
hello() {
let user = document.querySelector(".search").value;
let fullname = user.split(" ").join("");
let msg = "No Username Found";
const axios = require("axios");
axios.get("https://api.github.com/users/" + fullname)
.then(response => (this.infos = response.data))
.catch(error => alert(error + " " + msg))
},
reset() {
this.name = "";
}
}
};
</script>
infos is null so the div will not show.
Below i've added a created function, where I call hello().This will populate infos and show the div.
<script>
export default {
name: "HelloWorld",
data() {
return {
name: '',
infos: null,
}
},
created() {
hello();
},
methods: {
hello() {
let user = document.querySelector(".search").value;
let fullname = user.split(" ").join("");
let msg = "No Username Found";
const axios = require("axios");
axios.get("https://api.github.com/users/" + fullname)
.then(response => (this.infos = response.data))
.catch(error => alert(error + " " + msg))
},
reset() {
this.name = "";
}
}
};
</script>
try using computed method
<template>
<div>
<div class="log" v-for="info in infosComputed" v-bind:key="info" v-text="info.login">
Username
</div>
</div>
</template>
...
computed {
infosComputed: function() {
return this.infos;
}
}
data is not reactive
I am new to VueJs and Laravel. I am trying to build a chat app following this tutorial, and my problem is that I am not able to pass a CONTACT variable (object type) from ChatApp.vue to Conversation.vue. The console gives this error
[Vue warn]: Invalid prop: type check failed for prop "contact". Expected Object, got Null
found in ---> <MessageFeed> at resources/js/officer/components/MessageFeed.vue
<Conversation> at resources/js/officer/components/Conversation.vue
<ChatApp> at resources/js/officer/components/ChatApp.vue
<Root>
first the variable has to pass through ChatApp to Conversation and then to MessageFeed. I tried printing it on console, in ChatApp.vue, it printed the variable's property i.e. name but when I try to print it in Conversation.vue, it says it is null and also gives the above error.
please see the code below:
ChatApp.vue
<template>
<div class="chat-app">
<Conversation :contact="selectedContact" :messages="messages" #new="saveNewMessage"/>
<ContactsList :contacts="contacts" #selected="startConversationWith"/>
</div>
</template>
<script>
import Conversation from "./Conversation";
import ContactsList from "./ContactsList";
const axios = require('axios');
export default {
props: {
user: {
type: Object,
required: true
}
},
data() {
return {
selectedContact: null,
messages: [],
contacts: []
}
},
mounted() {
Echo.private(`messages.${this.user.id}`)
.listen('NewMessage', (e) => {
this.handleIncoming(e.message);
});
console.log(this.user);
axios.get('/officer/contacts')
.then((response) => {
this.contacts = response.data;
console.log("chatapp1: " + this.contacts[0].name);
})
.catch((error) => {
// handle error
console.log(error);
});
},
methods: {
startConversationWith(contact) {
axios.get(`/officer/conversation/${contact.id}`)
.then((response) => {
this.message = response.data;
this.selectedContact = contact;
console.log("chatapp: " + this.selectedContact.name);
});
},
saveNewMessage(message) {
this.messages.push(message);
},
handleIncoming(message) {
if (this.selectedContact && message.from === this.selectedContact.id) {
this.saveNewMessage(message);
return;
}
this.updateUnreadCount(message.from_contact, false);
},
updateUnreadCount(contact, reset) {
this.contacts = this.contacts.map((single) => {
if (single.id !== contact.id) {
return single;
}
if (reset)
single.unread = 0;
else
single.unread += 1;
return single;
})
}
},
components: {Conversation, ContactsList}
}
Conversation.vue
<template>
<div class="conversation">
<h1>{{ contact ? contact.name : 'Select a Contact' }}</h1>
<MessagesFeed :contact="contact" :messages="messages"/>
<MessageComposer #send="sendMessage"/>
</div>
</template>
<script>
import MessagesFeed from './MessageFeed';
import MessageComposer from './MessageComposer';
export default {
props: {
contact: {
type: Object,
default: null
},
messages: {
type: Array,
default: []
}
},
mounted() {
console.log("conversation: "+this.contact);
},
methods: {
sendMessage(text) {
if (!this.contact) {
return;
}
console.log(text);
axios.post('/conversation/send', {
contact_id: this.contact.id,
text: text
}).then((response) => {
this.$emit('new', response.data);
})
}
},
components: {MessagesFeed, MessageComposer}
}
MessageFeed.vue
<template>
<div class="feed" ref="feed">
<ul v-if="contact">
<li v-for="message in messages" :class="`message${message.to == contact.id ? 'sent' : 'received'}`"
:key="message.id">
<div class="text">
{{message.text}}
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
contact: {
type: Object,
required: true
},
messages: {
type: Array,
required: true
}
},
methods: {
scrollToBottom() {
setTimeout(() => {
this.$refs.feed.scrollTop = this.$refs.feed.scrollHeight - this.$refs.feed.clientHeight;
}, 1);
}
},
watch: {
contact(contact) {
this.scrollToBottom();
},
messages(messages) {
this.scrollToBottom();
}
}
}
please, let me know what am I missing here.
Because you konfigurated it to be an Object but you declared it on the initial state as null
selectedContact: null
That should be an object
It should look something like this i guess:
selectedContact: {
id:0
}
I fixed the issue.
This was due to axios.get() response thing. It was fetching data from database after a couple of minutes and the Conversation.vue and ContactList.vue were mounting immediately so they were not getting data.
With the help of my friend, it was fixed by loading the component only when axios have got all the contactlist from database and the very first contact have been passed to the Conversation.vue component i.e using v-if property of component.
I'm trying to pass data from vue component to blade file. I try to create props but it's didn't work for me. Is it any possible to pass the object to props to get the data? I'm new laravel. I want to pass data which subject, message, days, condition and module name to blade file(view).
I kept searching for this but couldn't find an answer that will make this clear.
Thanks!
blade.php
<div id="app">
<email-component
email_edit_route="{{ route('havence.automail.edit',['id'=>$mailTemplates->id]) }}"
>
</email-component>
</div>
Vue.js
<script>
import Vue from 'vue'
import axios from 'axios'
import MarkdownIt from 'markdown-it'
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
var msg_editor;
const md = new MarkdownIt({
linkify: true
})
export default {
props: ['email_creation_link', 'email_index_route', 'email_edit_route','conditions','modules'],
components: {
},
data() {
return {
template: {
subject: '',
message: '' ,
days: '',
condition_id: 1,
},
options:[
{
display:'Client Name',
actual:'Client name'
},
{
display:'Joined Date',
actual:'Joined date'
},
{
display:'Module Name',
actual:'Module name'
},
{
display:'Last Seen',
actual:'Last seen'
},
],
showName: false,
}
},
mounted(){
var self = this;
ClassicEditor
.create(document.querySelector( "#msg"),
{
})
.then(editor => {
msg_editor = editor;
editor.model.document.on( 'change:data', () => {
self.template.message = msg_editor.getData();
});
})
.catch(error => {
console.error(error);
})
},
methods: {
//Drag items
dragstart: function(item, e){
this.draggingItem = item;
e.dataTransfer.setData('text/plain', item.actual);
},
dragend: function(item,e) {
e.target.style.opacity = 1;
},
dragenter: function(item, e) {
this.draggingItem = item;
},
//content
replaceVariables(input)
{
let updated = input
return updated
},
//hidecontent
showHide: function(e)
{
console.log("Show "+e.target.value+ " fields")
this.showName = e.target.value !== ''
},
fetch()
{
//request data
axios.get(this.email_index_route,this.template)
.then((res) => {
this.template = res.data.template;
})
},
save()
{
//save data to db
axios.post(this.email_index_route, this.templates)
.then((res) => {
alert('Mail sent successfull!')
})
},
addToMail: function(type, text)
{
if (type == 'message') {
this.template.message += text;
msg_editor.setData(this.template.message);
}
},
//user name replace
replaceVariables() {
return this.replaceVariables(this.options || '')
},
}
}
</script>
Quick solution. : why not store data in local storage and fetch it from laravel blade ?
Next solution: you can fire global event from vue and listen on laravel blade .
why dont you send the data through ajax post call and get the data from the controller ?
then pass the data object to blade template.