Listening to API events in Vue - javascript

So I am struggling to find a way to listen to API events in vue. Tried to use different lifecycle hooks like mounted.
I am using this chat API: https://www.pubnub.com/docs/chat-engine/chats
And trying to listen when someone else has send out a message.
this.chat.on('message', (payload) => {
console.log(payload)
})
But I dont know where to put it. I used mounted but it didnt work. They also have an example for React here: https://github.com/pubnub/chat-engine-examples/blob/master/react/src/index.js and they are using the componentDidMount.
Does anyone where I can listen to the API events?

This works for me. I've auto created the application on pubnub https://www.pubnub.com/docs/chat-engine/getting-started#automagic-pubnub-setup
<template>
<div id="chat">
<div v-if="!isLoading">
<div>
<form #submit.prevent="sendMessage">
<input v-model="text"
type="text"
placeholder="Say something I am giving up on you" />
</form>
<div v-for="(message, index) in messages" :key="index">
{{message.text}}
</div>
</div>
</div>
<div v-else>loading...</div>
</div>
</template>
<script>
import ChatEngineCore from 'chat-engine';
const ChatEngine = ChatEngineCore.create({
publishKey: 'pub-c-e1fbdcd1-b3a9-4a67-b184',
subscribeKey: 'sub-c-b023a266-1628-11e8-92ea'
});
const now = new Date().getTime();
const username = ['user', now].join('-');
export default {
name: 'Chat',
data() {
return {
chat: null,
me: null,
isLoading: true,
text: '',
messages: []
};
},
created() {
this.init();
},
methods: {
init() {
ChatEngine.connect(
username,
{
signedOnTime: now
},
'auth-key'
);
ChatEngine.on('$.ready', data => {
this.isLoading = false;
this.me = data.me;
this.chat = new ChatEngine.Chat('ChatTest');
this.chat.on('message', message => {
this.messages.push(message.data);
});
});
},
sendMessage() {
this.chat.emit('message', {
text: this.text
});
}
}
};
</script>

Related

Vue.js - How to dynamically bind v-model to route parameters based on state

I'm building an application to power the backend of a website for a restaurant chain. Users will need to edit page content and images. The site is fairly complex and there are lots of nested pages and sections within those pages. Rather than hardcode templates to edit each page and section, I'm trying to make a standard template that can edit all pages based on data from the route.
I'm getting stuck on the v-model for my text input.
Here's my router code:
{
path: '/dashboard/:id/sections/:section',
name: 'section',
component: () => import('../views/Dashboard/Restaurants/Restaurant/Sections/Section.vue'),
meta: {
requiresAuth: true
},
},
Then, in my Section.vue, here is my input with the v-model. In this case, I'm trying to edit the Welcome section of a restaurant. If I was building just a page to edit the Welcome text, it would work no problem.:
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
This issue is that I need to reference the "welcome" part of the v-model dynamically, because I've got about 40 Sections to deal with.
I can reference the Section to edit with this.$route.params.section. It would be great if I could use v-model="restInfo. + section", but that doesn't work.
Is there a way to update v-model based on the route parameters?
Thanks!
Update...
Here is my entire Section.vue
<template>
<div>
<Breadcrumbs :items="crumbs" />
<div v-if="restInfo">
<h3>Update {{section}}</h3>
<div class="flex flex-wrap">
<div class="form__content">
<form #submit.prevent>
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
<div class="flex">
<button class="btn btn__primary mb-3" #click="editText()">
Update
<transition name="fade">
<span class="ml-2" v-if="performingRequest">
<i class="fa fa-spinner fa-spin"></i>
</span>
</transition>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { VueEditor } from "vue2-editor"
import Loader from '#/components/Loader.vue'
import Breadcrumbs from '#/components/Breadcrumbs.vue'
export default {
data() {
return {
performingRequest: false,
}
},
created () {
this.$store.dispatch("getRestFromId", this.$route.params.id);
},
computed: {
...mapState(['currentUser', 'restInfo']),
section() {
return this.$route.params.section
},
identifier() {
return this.restInfo.id
},
model() {
return this.restInfo.id + `.` + this.section
},
crumbs () {
if (this.restInfo) {
let rest = this.restInfo
let crumbsArray = []
let step1 = { title: "Dashboard", to: { name: "dashboard"}}
let step2 = { title: rest.name, to: { name: "resthome"}}
let step3 = { title: 'Page Sections', to: { name: 'restsections'}}
let step4 = { title: this.$route.params.section, to: false}
crumbsArray.push(step1)
crumbsArray.push(step2)
crumbsArray.push(step3)
crumbsArray.push(step4)
return crumbsArray
} else {
return []
}
},
},
methods: {
editText() {
this.performingRequest = true
this.$store.dispatch("updateRest", {
id: this.rest.id,
content: this.rest
});
setTimeout(() => {
this.performingRequest = false
}, 2000)
}
},
components: {
Loader,
VueEditor,
Breadcrumbs
},
beforeDestroy(){
this.performingRequest = false
delete this.performingRequest
}
}
</script>
Try to use the brackets accessor [] instead of . :
<vue-editor v-model="restInfo[section]"

How to determine if a user has closed his tab in order to fire a socket.io emit event?

Currently I'm trying to learn Socket.IO and in order to do that I've decided to try and build a chat application. Right now I have a functioning chat and I'm trying to create a list to the side of the chat that displays the usernames of all connected people.
When the user visits the page, an input field asks for a username. When a username is typed and submitted, this function executes:
setUsername(event){
event.preventDefault()
this.username = this.usernameInput
this.usernameInput = null
this.socket.emit('userJoined', this.username)
}
The function sets this.username to the inputted username and then emits that a user with this.username has joined and then this function pushes it to the array of online users:
this.socket.on('userJoined', (user) => {
this.onlineUsers.push(user)
});
So far so good, when users join the chat, their usernames do go inside the onlineUsers array, however, I can't figure out how to remove them out of this array when they close their tab. In order to do that I'd have to fire this.socket.emit('userLeft', this.username) but I am not sure how am I supposed to fire it. Is there a function in which I can put this code that is always executed when a user closes his tab?
I've supplied my full code below:
<template>
<div class="home">
{{ onlineUsers }}
<div class="wrapper">
<div class='username-container' v-if='!this.username'>
<form class='username-form' #submit='setUsername($event)'>
<input class='username-input' v-model="usernameInput" placeholder="Pick a username">
<div class='enter-chat' #click='setUsername($event)'>Enter chat</div>
</form>
</div>
<div class='chat-container' v-else>
<div class="chat">
<div class="messages">
<div class='message-container' v-for='message in messages'>
<div class="username-date-container">
<p class='username'>{{ message.username }}</p>
<p class='date'>{{ getTimeAndDate(message.time) }}</p>
</div>
<p class='message'>{{ message.message }}</p>
</div>
</div>
</div>
<form #submit='sendMessage($event)'>
<input class='message-input' v-model="message" placeholder="Type a message">
</form>
</div>
</div>
</div>
</template>
<script>
import io from 'socket.io-client'
import moment from 'moment'
export default {
data(){
return {
socket: io.connect('http://localhost:3000'),
usernameInput: null,
username: null,
messages: [],
message: null,
onlineUsers: []
}
},
methods: {
setUsername(event){
event.preventDefault()
this.username = this.usernameInput
this.usernameInput = null
this.socket.emit('userJoined', this.username)
},
sendMessage(event){
event.preventDefault()
let fullMessage = {
username: this.username,
message: this.message,
time: Date.now()
}
this.socket.emit('message', fullMessage)
this.message = null
},
getTimeAndDate(timestamp){
return moment(timestamp).subtract(10, 'days').calendar();
}
},
mounted(){
this.socket.on('message', (message) => {
this.messages.push(message)
});
this.socket.on('userJoined', (user) => {
this.onlineUsers.push(user)
});
this.socket.on('userLeft', (user) => {
this.onlineUsers.pop(user)
});
}
}
</script>
try this:
window.addEventListener("beforeunload", function(event) { ... });
window.onbeforeunload = function(event) { ... };
https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload

How to get event by id from vuex and open in detail page

i am continuing this app Read data from firebase in vuex. I got stuck when i want open a details page with a event. Until now i could did is, when for the first time i click on an event it’s open a details page of the event clicked with an error in the console, but when i reload the detail page, it doesn’t reload data.
This is my store.js
state:{
events:[],
},
mutations:{
setEvent(state,payload){
state.events = payload
},
setEventDetails(state,payload){
state.events = payload
},
},
actions:{
addEvent(context,payload){
firebase.database().ref('eventos/').push(payload)
.then((event)=>{
context.commit('setEvent',event)
}).catch(err =>{
console.log(err)
})
},
getEvents({commit}){
let events = [];
firebase.database().ref('eventos')
.on('value',event =>{
event.forEach(data =>{
events.push({ "id": data.key, ...data.val() })
});
commit('setEvent',events)
});
},
getEventDetails(context,payload){
let event = context.getters.eventDetails(payload.id);
context.commit('setEventDetails',event)
}
},
getters:{
eventDetails: (state) => (id) => {
return state.events.find((event) => event.id === id);
},
eventList: state => {
return state.events
},
}
This is my ReadEvents.vue from where i click to open in details page a event.
<template>
<div>
<div v-for="(event,id) in eventList" :key="id">
{{event}}
<button v-on:click="openEventDetails(event.id)">Details</button>
</div>
</div>
</template>
<script>
export default {
name: "ReadEvents",
computed:{
eventList(){
return this.$store.getters.eventList;
}
},
mounted() {
this.$store.dispatch('getEvents')
},
methods:{
openEventDetails(id){
this.$router.push({
name: 'Detalles',
params:{id:id}
})
}
}
And this is the EventDetails.vue where render the details of a event.
<template>
<div>
{{search}}
</div>
</template>
<script>
export default {
props:['id'],
name: "EventDetails",
computed:{
search() {
return this.$store.getters.eventDetails(this.id)
},
},
created() {
console.log(this.search)
},
mounted() {
this.$store.dispatch('getEventDetails')
},
}
Here is a result when i open from ReadEvents.vue
And this if after reload the page
2:
You're calling this.$store.dispatch('getEvents') from mounted in ReadEvents.vue.
But you don't do the same in EventDetails.vue. This means that when you navigate directly to the route that uses EventDetails.vue the vuex store is not populated with events.
One solution would be to create an action that gets an event's details and call it from EventDetails.vue mounted method.

how to move items to another component by click - vue.js

I was wondering how can I move my items -> book, from one component to another. I took this books from api and I show them in the list, so I have an ID.
<template>
<v-flex v-for="(book, index) in allBooks">
<div>Title: {{ book.title }}</div>
<i #click="markAsFavorite(book)" :class="{isActive: isMark}" class="fas fa-heart"></i>
</template>
//component books
<script>
name: 'Books',
data () {
return {
allBooks: [],
isMark: false,
favouriteBooks: []
}
},
mounted() {
axios.get("https://www.googleapis.com/books/v1/volumes?q=id:" + this.id)
.then(response => {
console.log(response)
this.allBooks = response.data.items.map(item => ({...item, isMark: false}))
console.log(this.allBooks)
})
.catch(error => {
console.log(error)
})
},
methods: {
markAsFavorite(book) {
this.isMark = !this.isMark
let favouriteAllBooks = this.favouriteBooks.push(book => {
book.id = // i dont know what?
})
},
}
</script>
//component favourite
<template>
<div class=showFavouriteBook>
<p></p>
</div>
</template>
I tried to compare this marked book ID to something, and then this array with marked books show in second template favourite. But I have no idea how to do this. Maybe somebody can prompt me something?
You should use a global eventBus for that. An 'eventBus' is another instance of Vue which is used to pass data via components tied to the main application.
At the root script of your application append the following:
const eventBus = new Vue({
data: function() {
return {
some_var: null,
}
}
});
You can use Vue mixin to have your event bus accessible globally easily:
Vue.mixin({
data: function() {
return {
eventBus: eventBus,
}
}
});
Now when you want to pass data between components you can use the bus:
Component 1
// for the sake of demo I'll use mounted method, which is invoked each time component is mounted
<script>
export default {
mounted: function() {
this.eventBus.some_var = 'it works!'
}
}
</script>
Component 2
<template>
<div>
{{ eventBus.some_var }} <!-- it works -->
</div>
</template>
In addition you can use $emit and $on.
Component 1
// for the sake of demo I'll use mounted method, which is invoked each time component is mounted
<script>
export default {
mounted: function() {
// emit 'emittedVarValue' event with parameter 'it works'
this.eventBus.$emit('emittedVarValue', 'it works!')
}
}
</script>
Component 2
<template>
<div>
{{ some_var }} <!-- "it works" once eventBus receives event "emittedVarValue" -->
</div>
</template>
<script>
export default {
data: function() {
return {
some_var: null
}
},
mounted: function() {
// waiting for "emittedVarValue" event
this.eventBus.$on('emittedVarValue', (data)=>{
this.some_var = data;
})
}
}
</script>
Hope this answer helps you.

Computed property in Vue is not triggering a watch

According to this post, it shouldn't be a problem to watch a computed property. And yet my code isn't working.
<template>
<div v-if="product" class="section">
<form>
<div class="control"><input type="text" class="input" v-model="title"></div>
<div class="control"><input type="text" class="input" v-model="description"></div>
</form>
</div>
</template>
<script>
export default {
data() {
return {
title: null,
description: null
}
},
computed: {
product() {
// const payload = { collection: 'products', id: this.$route.params.productId }
// return this.$store.getters.objectFromId(payload)
console.log('working')
return { title: 'Awesome Title', description: 'Awesome Description' }
}
},
watch: {
product() {
this.title = this.product.title,
this.description = this.product.description
}
}
}
</script>
I'm expecting the watch to trigger when product is returned, but it doesn't.
I could set the properties in the computed property like so:
computed: {
product() {
const payload = { collection: 'products', id: this.$route.params.productId }
const product = this.$store.getters.objectFromId(payload)
this.title = product.title
this.description = product.description
return product
}
}
But then the compiler gives me a warning: error: Unexpected side effect in "product" computed property
Accordingly to OP's comments, his intention is to get and load some initial data.
The common way to achieve this behavior is to place it inside created or mounted vuejs lifecycle hooks.
<template>
<div v-if="product" class="section">
<form>
<div class="control"><input type="text" class="input" v-model="title"></div>
<div class="control"><input type="text" class="input" v-model="description"></div>
</form>
</div>
</template>
<script>
export default {
data() {
return {
title: '',
description: ''
}
},
created() {
this.getInitialData();
this.foo();
console.log("created!");
},
methods: {
getInitialData: function(){
const payload = {
collection: 'products',
id: this.$route.params.productId
};
var product = this.$store.getters.objectFromId(payload);
this.title = product.title;
this.description = product.description;
},
foo: function(){// ...}
},
}
</script>
Your structure is a bit all over the place. product is a computed, so it runs whenever it's source values change. (You have no control over when it runs.) It shouldn't have side effects (assignments this.description, this.title), or trigger network requests.
The code in product is fetching your source data. This belongs in methods, linked explicitly to a user action or a lifecyle event.
Why do you need to copy your data (this.description = product.description in watch:product)? Vue works best when you have your data (your app state) outside Vue, in a global variable say. Then your Vue components just transparently reflect whatever the app state is at a given moment.
Hope this helps.
Try the following:
watch: {
product: {
immediate: true,
handler(value) {
updateCode();
}
}
}

Categories

Resources