Emitting global events from websocket listener - javascript

I want to contribute to a project - it's written in Vue, and I am a beginner in Vue.
I have two components - Setup and MainApp
Both will need to update some state based on different messages from the websocket. Some websocket messages will affect the former, some the latter.
Vue doesn't know services, so I thought I'd just create a custom component, with empty <template>. instantiate the websocket there and then issue an this.emit() every time a new message occurs in the listener.
Both other components would listen to the emits and would be able to react.
Unfortunately, I can't get the websocket component to work.
main.js:
import Ws from './WsService.vue';
//other imports
const routes = [
//routes
]
const router = new VueRouter({
routes // short for `routes: routes`
})
const app = new Vue({
router
}).$mount('#app')
//I thought this to be the way to instantiate my webSocket service:
const WsService = new Vue({
el: '#WsService',
components: { Ws }
});
index.html
<body>
<div id="app">
<div id="WsService"></div>
<router-link to="/setup">Setup</router-link>
<router-link to="/main-app">Main App</router-link>
<router-view></router-view>
</div>
<script src="/dist/demo-app.js"></script>
</body>
the websocket "service":
<template>
</template>
<script>
const PORT_LOCAL = 9988;
var ws = new WebSocket("ws://localhost:" + PORT_LOCAL);
ws.onopen = function() {
ws.send('{"jsonrpc":"2.0","id":"reg","method":"reg","params":null}');
};
ws.onerror = function(e) {
console.log("error in WebSocket connection!");
console.log(e);
};
export default {
data() {
return {
}
},
created() {
var self = this;
ws.onmessage = function(m) {
var msg = JSON.parse(m.data);
switch(msg.id) {
// result for address request
case "reg":
self.$emit("reg_received", msg.result);
break;
case "send":
self.$emit("send_received", msg.result);
break;
case "subscribe":
self.$emit("subscribe_received", msg.result);
break;
default:
console.log(msg);
break;
}
}
},
methods: {
},
send(id, method, params) {
ws.send('{"jsonrpc":"2.0","id":"' + id + '","method":"' + method + '","params":null}');
}
}
}
</script>
Send for example from main app (this seems to work):
import WsSvc from './WsService.vue';
export default {
data() {
//
},
subscribe() {
let jsonrpc = "the jsonrpc string";
WsSvc.send(jsonrpc);
}
}
Listening to emit:
export default {
data() {
//
},
created() {
this.$on("reg_received", function(result){
//do smth with the result
});
}
}
Wit this configuration, the created hook actually never gets called - and thus I'll never hit the onmessage listener. The reason to have a custom component I thought was that I would have access to the emit function.
It feels I am making it more complicated than it should be but I haven't managed yet to get it right. The solution doesn't need to follow this approach.

There's no need for a socket specific component in this case. What I have done in the past on a couple projects is implement an API or store object that handles the socket messages and then import that API or store into the components that need it. Also in a similar answer, I show how to integrate a WebSocket with Vuex.
Here is an example that combines the concept of using Vue as an event emitter with a web socket that can be imported into any component. The component can subscribe and listen to the messages it wants to listen to. Wrapping the socket in this way abstracts the raw socket interface away and allows users to work with $on/$off subscriptions in a more typically Vue fashion.
Socket.js
import Vue from "vue"
const socket = new WebSocket("wss://echo.websocket.org")
const emitter = new Vue({
methods:{
send(message){
if (1 === socket.readyState)
socket.send(message)
}
}
})
socket.onmessage = function(msg){
emitter.$emit("message", msg.data)
}
socket.onerror = function(err){
emitter.$emit("error", err)
}
export default emitter
Here is an example of that code being used in a component.
App.vue
<template>
<ul>
<li v-for="message in messages">
{{message}}
</li>
</ul>
</template>
<script>
import Socket from "./socket"
export default {
name: 'app',
data(){
return {
messages: []
}
},
methods:{
handleMessage(msg){
this.messages.push(msg)
}
},
created(){
Socket.$on("message", this.handleMessage)
},
beforeDestroy(){
Socket.$off("message", this.handleMessage)
}
}
</script>
And here is a working example.

Hey this should work for you better and easy
This my example with .vue file
yourVueFile.Vue
<template>
// key in your template here
</template>
<script>
export default {
//use the created() option to execute after vue instance is created
created() {
let ws = new WebSocket("yourUrl");
ws.onopen = e => {
ws.send(
JSON.stringify({ your json code })
);
ws.onmessage = e => {
let data = JSON.parse(e.data);
// the this.$data get your data() options in your vue instance
this.$data.dom = data;
};
};
},
data() {
return {
dom: core
};
},
methods: {
}
};
</script>

Related

How to call the function in a separate JS file through a vue instances [duplicate]

This question already has answers here:
"Uncaught SyntaxError: Cannot use import statement outside a module" when importing ECMAScript 6
(31 answers)
Closed 1 year ago.
I have a javascript function in a separate js file toaster-message.js, the file is in wwwroot/js of ASP.net application, which calls the bootstrap toaster.
toaster-message.js.
showCompleteToast: (message) => {
const toastElm = document.getElementById('#complete-toast');
const toast = new bootstrap.Toast(toastElm)
toast.show();
}
I want to call the showCompleteToast() from my vue instances. I am creating the vue instances with Direct script Include.
I don't want to add any Vue dependency to the function outside the Vue instances. So what is the proper way to call the function in the external js file that is outside the Vue instances?
#section scripts {
<script>
const app = new Vue({
el: "#app",
data() {
return {
}
},
methods: {
showToast: function(){
//I want to call the show toast from here
},
submit: async function () {
try {
this.showToast();
} catch (error) {
console.log(error)
}
}
}
});
</script>
}
When i try to import using:
import { showCompleteToast } from "~/lib/js/toater-message.js"
while using:
export default {
showCompleteToast: (message) => {
const toastElm = document.getElementById('#complete-toast');
const toast = new bootstrap.Toast(toastElm)
toast.show();
},
// ... other methods here
};
I get the error as:
“Uncaught SyntaxError: Cannot use import statement outside a module”
I tried to to import using:
<script type="module">
import { showCompleteToast } from "../../wwwroot/js/toaster-message.js"
alert(showCompleteToast)
</script>
This gave the error as:
GET https://localhost:44307/wwwroot/js/toaster-message.js net::ERR_ABORTED 404
I'm not very familiar with php but typically you can import JavaScript files and then work with their contents. The only requirement is that the imported files need to have exporting defined.
// toaster-message.js
export default {
showCompleteToast: (message) => {
const toastElm = document.getElementById('#complete-toast');
const toast = new bootstrap.Toast(toastElm)
toast.show();
},
// ... other methods here
};
Then pull it into your Vue file like this:
#section scripts {
<script>
import { showCompleteToast } from "..path/to/toaster-message.js"
const app = new Vue({
el: "#app",
data() {
return {
}
},
methods: {
showToast: function(){
//I want to call the show toast from here
showCompleteToast();
},
submit: async function () {
try {
this.showToast();
} catch (error) {
console.log(error)
}
}
}
});
</script>
}

Websockets in Vue/Vuex (how to receive emissions from server)

So until now I just socket.io-client to do communication to a WebSocket in my Vue component.
Now I am adding Vuex to the project and declared a Websocket like this
Vue.use(new VueSocketIO({
debug: true,
connection: 'http://192.168.0.38:5000',
}));
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
1) Should i have stuff like emitting some messages in the component themselves now or in the store?
2) Before I introduced the changes I could do something like this:
socket.on('connect', function () {
console.error('connected to webSocket');
socket.emit('my event', { data: 'I\'m connected!' });
});
socket.on('my response', function(data){
console.log('got response');
console.log(data.data);
});
When sending the "my event", the flask server would respond with "my response". Now I am trying the same thing from a component after the changes like this.
this.$socket.emit('my_event', { data: 'I\'m connected!' });
console.error('send to websocket ');
this.$options.sockets.my_event = (data) => {
console.error('received answer ');
console.error(data);
};
The my_event reaches my flask server however I don't get the response receiving to work. What am I doing wrong?
Also because I was asking about whether I should put this in the component or the store, I found stuff like this for the store:
SOCKET_MESSAGECHANNEL(state, message) {
state.socketMessage = message
}
The explanation was "So, for example, if your channel is called messageChannel, the corresponding Vuex mutation would be SOCKET_MESSAGECHANNEL" and it is from this site https://alligator.io/vuejs/vue-socketio/.
I think I don't really get what a channel is at this point. Is the my_response I emit from the flask server also a channel?
Thanks for your help in advance!
EDIT: So now I am trying to listen and emit to a websocket from my store. For this I tried the following: In main.js I have this part:
Vue.use(new VueSocketIO({
debug: true,
connection: SocketIO('http://192.168.0.38:5000'),
vuex: {
store,
actionPrefix: 'SOCKET_',
mutationPrefix: 'SOCKET_',
},
}));
Then in my store.js I have the following:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0,
title: 'title from vuex store',
isConnected: false,
},
mutations: {
increment(state) {
state.count += 1;
},
emitSth(state) {
this.sockets.emit('my_event', { data: 'I\'m connected!' });
console.log(state.count);
},
SOCKET_my_response(state) {
state.isConnected = true;
alert(state.isConnected);
},
SOCKET_connect(state) {
state.isConnected = true;
alert(state.isConnected);
},
},
});
And in my component I have this script:
export default {
name: 'ControlCenter',
data() {
return {
devices: [{ ip: 'yet unknown' }], // placeholder so line 12 does not throw error before actual device info fetched
thisDeviceIndex: 0,
currentLayoutIndex: 0,
layouts: [],
};
},
computed: mapState([
'title',
'count',
]),
components: {
DNDAssign,
FirstPage,
},
methods: {
// mapMutation helper let's us use mutation from store via this instead of this.$store
...mapMutations([
'increment',
'emitSth',
]),
incrementMutation() {
this.increment();
},
emitEvent() {
this.emitSth();
},
// some other stuff here
},
created() {
// inital fetching of layouts
console.log('fetching layouts from backend');
this.getAllLayouts();
console.log(this.$socket);
},
};
I also have a button for the triggering of the emit which is
<b-button
type="button"
variant="success"
v-on:click="emitEvent()"
>
emit event
</b-button>
The connected in the store gets triggered, however I get the following errors for the emitting:
"TypeError: Cannot read property 'emit' of undefined"
"Cannot read property 'emit' of undefined"
Also I am not sure about the naming in the mutations. If I have this mutationPrefix, shouldn't it be enough to just use connect instead of SOCKET_connect?
First of all, if you are using Vue-Socket.io version 3.0.5>, uninstall it and install version 3.0.5
npm uninstall vue-socket.io
npm install vue-socket.io#3.0.5
then lock the version in packege.json: "vue-socket.io": "3.0.5", latest update seems to breaks the library, read more here
Now to receive events from socket.io server, you can do:
this.sockets.subscribe("my response", (data) => {
console.log(data);
});
or if want put listener on component level, you need to add sockets object on the component export, for example:
export default {
...
sockets: {
"my response": function (data) {
console.log(data);
}
}
...
}
Since you are not using Vuex Integration on the VueSocketIO, you dont need to put additional function in store mutation. If you want to use Vuex integration on VueSocketIO, you need to add vuex object when declaring the VueSocketIO class.
Here's the basic example for main.js
// Set Vue to use Vuex
Vue.use(Vuex);
// Create store
const store = new Vuex.Store({
state: {
someData: null
},
getters: {},
actions: {
"SOCKET_my response"(context, data) {
// Received `my response`, do something with the data, in this case we are going to call mutation "setData"
context.commit("setData", data);
}
}
mutations: {
["setData"](state, data) {
state.someData = data; // Set it to state
}
}
});
// Set Vue to use VueSocketIO with Vuex integration
Vue.use(new VueSocketIO({
debug: true,
connection: 'http://192.168.0.38:5000',
vuex: {
store,
actionPrefix: "SOCKET_"
}
}));
new Vue({
router,
store
render: h => h(App)
}).$mount("#app");
If you need example on Vuex Integration, you can check my example app that uses Vue and Vue-Socket.io with Vuex integration.te

Async data change when using server side render in Vue

I want to use Vue in server side rendering, but the content data inside template have to request from the other CMS server.
<template>
<h1>{{ content.heading }}</h1>
</template>
<script>
export default {
data() {
return {
content: {
heading: ''
}
}
},
created() {
axios
.get(CONTENT_RESOURCE)
.then(content => this.content = content);
}
}
</script>
Due to axios.get is an async request, server will send empty content before request complete.
Use curl to request content:
curl 'URL';
# It got <h1></h1>,
# but I want <h1>Something here</h1>
How do I make sure it can render with CMS content data in server side?
According to vue-hackernews-2.0 example, src/server-entry.js will detect preFetch function in current route component.
So, just add a preFetch function in current route component and save the data to Vuex store.
<template>
<h1>{{ content.heading }}</h1>
</template>
<script>
const fetchContent = store =>
axios
.get(CONTENT_RESOURCE)
.then(content => store.dispatch('SAVE_CONTENT', content));
export default {
computed: {
content() {
return this.$store.YOUR_CONTENT_KEY_NAME
}
},
preFetch: fetchContent, // For server side render
beforeCreate() { // For client side render
fetchContent(this.$store);
}
}
</script>
You have to make following changes in the code:
var demo = new Vue({
el: '#demo',
data:{
content : {heading: ""}
},
beforeMount() {
var self = this;
setTimeout(function(){
self.content.heading = "HI"
}, 100)
}
})
Here is a working fiddle.

Error in running unit test for Vue webapp

I am writing a webapp with VueJs, I am trying to setup unit test for it, I got inspired from vue-mdl unit-tests. But the tests are not running properly for my code and I am getting vm.$el as undefined, so not able to move forward at all.
Here is the component, I am trying to test:
Confirmation.vue
<template>
<div>
Your order has been confirmed with the following details.
</div>
</template>
<script type="text/javascript">
export default {
data () {
return {
data_from_pg: null
}
}
}
</script>
and here is test for it, which fails
Confirmation.spec.js
import Confirmation from 'src/components/Confirmation'
import { vueTest } from '../../utils'
describe('Confirmation', () => {
let vm
let confirmation
before(() => {
vm = vueTest(Confirmation)
console.log('vm.$el ' + vm.$el) => this prints undefined
confirmation = vm.$el.querySelector('#confirmation') => so this line gives error
// confirmation = vm.$('#confirmation')
})
it('exists', () => {
confirmation.should.exist
confirmation.should.be.visible
})
})
utils.js
export function vueTest (Component) {
const Class = Vue.extend(Component)
Class.prototype.$ = function (selector) {
return this.$el.querySelector(selector)
}
Class.prototype.nextTick = function () {
return new Promise((resolve) => {
this.$nextTick(resolve)
})
}
const vm = new Class({
replace: false,
el: 'body'
})
return vm
}
My complete code is available here, with all the test config, which I have tried to change many times, but could not figure out how to make it work. Please let me know if you see some error somewhere.
The vueTest function in utils is trying to load the Vue instance into the body tag:
const vm = new Class({
replace: false,
el: 'body'
})
return vm
The unit tests do not load index.html as an entry point into the app, but rather the individual components that you want to test; Therefore, you do not have access to document or html elements and the component is never mounted. I'd suggest using vm.$mount():
If elementOrSelector argument is not provided, the template will be rendered as an off-document element.
You could change the above lines to something like the following
const vm = new Class();
vm.$mount();
return vm;
Your tests should now have access to the $el property.

Vue.js - Global Data from AJAX Call

I'm giving Vue.js a try and so far I'm loving it because it's much simpler than angular. I'm currently using vue-router and vue-resource in my single page app, which connects to an API on the back end. I think I've got things mostly working with a the primary app.js, which loads vue-router and vue-resource, and several separate components for each route.
Here's my question: How do I use props to pass global data to the child components when the data is fetched using an asynchronous AJAX call? For example, the list of users can be used in just about any child component, so I would like the primary app.js to fetch the list of users and then allow each child component to have access to that list of users. The reason I would like to have the app.js fetch the list of users is so I only have to make one AJAX call for the entire app. Is there something else I should be considering?
When I use the props in the child components right now, I only get the empty array that the users variable was initialized as, not the data that gets fetched after the AJAX call. Here is some sample code:
Simplified App.js
var Vue = require('vue');
var VueRouter = require('vue-router')
Vue.use(VueRouter);
var router = new VueRouter({
// Options
});
router.map({
'*': {
component: {
template: '<p>Not found!</p>'
}
},
'/' : require('./components/dashboard.js'),
});
Vue.use(require('vue-resource'));
var App = Vue.extend({
ready: function() {
this.fetchUsers();
},
data: function() {
return {
users: [],
};
},
methods: {
fetchUsers: function() {
this.$http.get('/api/v1/users/list', function(data, status, response) {
this.users = data;
}).error(function (data, status, request) {
// handle error
});
}
}
});
router.start(App, '#app')
Simplified app.html
<div id="app" v-cloak>
<router-view users = "{{ users }}">
</router-view>
</div>
Simplified dashboard.js
module.exports = {
component: {
ready: function() {
console.log(this.users);
},
props: ['users'],
},
};
When dashboard.js gets run, it prints an empty array to the console because that's what app.js initializes the users variable as. How can I allow dashboard.js to have access to the users variable from app.js? Thanks in advance for your help!
p.s. I don't want to use the inherit: true option because I don't want ALL the app.js variables to be made available in the child components.
I believe this is actually working and you are being misled by the asynchronous behavior of $http. Because your $http call does not complete immediately, your console.log is executing before the $http call is complete.
Try putting a watch on the component against users and put a console.log in that handler.
Like this:
module.exports = {
component: {
ready: function() {
console.log(this.users);
},
props: ['users'],
watch: {
users: {
handler: function (newValue, oldValue) {
console.log("users is now", this.users);
},
deep: true
}
}
}
};
In the new version of Vue 1.0.0+ you can simply do the following, users inside your component is automatically updated:
<div id="app" v-cloak>
<router-view :users="users"></router-view>
</div>

Categories

Resources