Emiting VueJS event from a JavaScript class - javascript

I'm building a simple subscription model in my Vuejs application where I'm having few difficulties in maintaining the local storage. I created a class which is responsible for storage of token/key which is required in the application.
export default class session {
constructor(STORAGE, INACTIVITY_SESSION) {
this.STORAGE = STORAGE;
this.INACTIVITY_SESSION = INACTIVITY_SESSION;
this.key = 'session-key';
}
start() {
//
}
setAll(all) {
}
getAll() {
}
renew(sessionId){
}
destroy(){
//Some code for removal of keys.
eventBus.$emit('destroyed');
}
resetTimer() {
}
}
I want that whenever destroy function is called it should call an event, and this event can be captured in my application and perform action as desired. But somehow the event is emitting. I'm having import event file:
import {eventBus} from './_events.js';
and my _event.js file has:
import Vue from 'vue';
/**
* Event bag to be used globally
*/
export const eventBus = new Vue();
This event bag is working inside any .vue file or vue-templates but not working in this session class. Moreover I'm importing class in vue application as:
import session from 'NitsModels/_session';
window.Vue = require('vue');
const sessions = new session(process.env.MIX_STORAGE_PERSIST, process.env.MIX_INACTIVITY_SESSION);
sessions.start();
//Some code for routing, vuex-store and definition of rendering component....
const layoutTwo = new Vue({
el: '#layout-three',
router: router,
store,
render:h=>h(adminLayout)
});
And I'm listening to this event inside my template component:
<template>
<div style="height: 100%;">
<div v-if="logged" style="height: 100%;">
<layout-three></layout-three>
</div>
<div v-else style="height: 100%;">
<router-view></router-view>
</div>
</div>
</template>
<script>
import {eventBus} from 'NitsModels/_events.js';
export default {
name: "admin",
data() {
return {
logged: false,
}
},
created() {
eventBus.$on('destroyed', () => {
this.$router.push({path: '/'});
this.logged = false
});
},
watch: {
'$route' (to, from) {
// react to route changes...
if(to.meta.requiresAuth && !from.meta.requiresAuth)
this.logged = true
if(!to.meta.requiresAuth && from.meta.requiresAuth)
this.logged = false
}
}
}
</script>
<style lang="scss">
</style>
Any suggestions are welcomed. Thanks.

Related

Pass data from plain javascript to vue

I have an app with plain JS and Vue in one file. I need to pass a variable value from JS to Vue.
0. All code in one file:
<script>
plain js
var plainJS = 100;
</script>
<script>
var app = new Vue({
el: '#vue-app',
....
</script>
main functionality of the app is on plain js. Vue does a small part with UI.
with js I can catch if one of my elements changed position (a dot on the screen)
I need fire popup(some alert) if checkBox is selected but the Dot wasn't moved.
checkBox is a Vue element
I can pass data from Django to Vue
this.vueVar = {{ djangoVar|safe }}
So how to pass
*var plainJS = 100;*
to vue app from plain JS part of the code?
Can you give me a simple way to set vueVar = plainJS?
UPDATE:
function from plain JS
function isDotMoved(length){
if(length != 0){
console.log(length)
return true;
}
return false;
};
so this function works when I grab and move my dot on the screen.
As well, I have a function in Vue part:
isDotsMoved(){
this.dotMoved = isDotMoved(length); // function from plain JS
console.log('moved', this.dotMoved)
if(!this.dotMoved){
toastr.info('Dot Moved');
}
},
I call this function onClick. It should fire Alert if dots were moved.
I use another function the same way:
function videoPause() {
inputVideo.pause();
};
And called it inside of my Vue part:
videoPauseVue() {
videoPause(); //function from plain JS
};
How can I do the same for isDotsMoved()?
First, i add isDotMoved function in the index.html script tag and declare it on window variable that can access anywhere in your code because it is global scope.
<script>
window.plainFunc = (function () {
return {
isDotMoved: function (length) {
if (length != 0) {
console.log(length);
return true;
}
return false;
}
};
})();
</script>
Then in vue I access it throught window variable
<template>
<div id="app">
<h1>Use Function from plainJS</h1>
<button #click="handleClick">Click here to invoke function isDotMove from script</script></button>
</div>
</template>
<script>
function isDotsMoved() {
let length = 10;
let dotMoved = window.plainFunc.isDotMoved(length); // function from plain JS
console.log("moved", dotMoved);
if (!dotMoved) {
alert("Dot Moved");
}
}
export default {
name: "App",
methods: {
handleClick: function () {
isDotsMoved();
},
},
};
</script>
<style>
</style>
Check my example on codebox: https://codesandbox.io/embed/vue-js-font-awesome-1--getting-started-forked-d8xist?fontsize=14&hidenavigation=1&theme=dark
You can access a ref on the root component if you store a variable of what createApp returns. Then each time you would update your plainJS var, also reassign a matching property (ref) on the "app" object. For the initial value you may use a "root prop" which is the 2nd param of the createApp function.
main.js
import { createApp } from "vue";
import App from "./App.vue";
var plainJS = 100;
const myApp = createApp(App, { plainJS: plainJS }).mount("#app");
setInterval(() => {
//interval used here to simulate a value that changes at arbitrary times
plainJS++;
myApp.varFromOutsideVue = plainJS; // 👀 this updates the ref
}, 500);
App.vue
<template>
<h1>{{ varFromOutsideVue }}</h1>
</template>
<script>
import { onMounted, onUnmounted, ref } from "vue";
export default {
name: "App",
props: {
plainJS: { type: Number },
},
setup(props) {
const varFromOutsideVue = ref(props.plainJS);
return {
varFromOutsideVue,
};
},
};
</script>
https://codesandbox.io/s/eager-rubin-6fv7p7?file=/src/main.js
Another option (see my other answer for a more direct solution) is to use the browser's native event system to "subscribe" to changes to the variable from within your vue app. Each time the value changes you emit a custom event and there is an event listener within your vue app set up to listen to those changes and update a reactive ref.
main.js
import { createApp } from "vue";
import App from "./App.vue";
var plainJS = 100;
function fireVarChangeEvent() {
const newEvent = new CustomEvent("varchanged", {
detail: plainJS
});
window.dispatchEvent(newEvent);
}
setInterval(() => {
//interval used here to simulate a value that changes at arbitrary times
plainJS++;
fireVarChangeEvent(); // call this function after each time plainJs var is updated
}, 500);
createApp(App, { plainJS: plainJS }).mount("#app"); //pass in the first value of plainJS as a prop, this will not stay reactive, hence the custom event
App.vue
<template>
<h1>{{ varFromOutsideVue }}</h1>
</template>
<script>
import { onMounted, onUnmounted, ref } from "vue";
export default {
name: "App",
props: {
plainJS: { type: Number },
},
setup(props) {
const varFromOutsideVue = ref(props.plainJS);
function updateVar(e) {
varFromOutsideVue.value = e.detail;
}
onMounted(() => {
window.addEventListener("varchanged", updateVar);
});
onUnmounted(() => {
window.removeEventListener("varchanged", updateVar);
});
return {
varFromOutsideVue,
};
},
};
</script>

How to add multiple components dynamically at run time not globally but locally

Hi i have a situation where i need to register multiple components at run time within a file.
Display.vue
<template>
<div>
<component v-if="currentComponent" :is="currentComponent">
</component>
</div>
</template>
<script>
import Vue from 'vue';
export default {
data(){
return{
currentComponent:null,
}
},
methods:{
load(e){
for(let key in e.components){
Vue.component(key,e.components[key]);
}
this.$nextTick(() =>{
this.currentComponent = e.components.container;
});
},
},
created(){
document.body.addEventListener('component-ready',this.load, false);
},
}
</script>
in my above file how i want my components to be loaded something like shown below:
components:{
container: component Object{},
header: component Object{},
body: component Object {},
footer: component Object {},
}
here is how i'm dispatching event to above file Display.vue
const event = new Event('component-ready');
event.components = {
container: component Object{},
header: component Object{},
body: component Object {},
footer: component Object {},
};
document.body.dispatchEvent(event);
Execution sequence:
event dispatch component-ready
eventlisterner in created of file Display.vue will call load method
Problem: in my current approach components are registered globally, that i want to avoid. i want to register all components to Display.vue file only
To locally register the components on the fly, you can copy the component definitions into this.$options.components:
export default {
methods: {
load(e) {
this.$options.components = e.components 👈
this.$nextTick(() => {
this.currentComponent = e.components.container
})
},
},
}
demo

VueJS; wait for element before running local JavaScript File

I have a few components, javascript, and elements that needs to be ran in a certain order.
1st - opensheetmusicdisplay.min.js which I have in my index.html file. This isn't an issue.
2nd - <div id="xml">
3rd - xml-loader.js which depends on both the "xml" div and opensheetmusicdisplay.min,js
This is the index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<script rel="preload" src="<%= BASE_URL %>js/osmd/opensheetmusicdisplay.min.js"></script>
</head>
<body>
<div id="xml2">words go here</div>
<div id="app"></div>
</body>
</html>
And this is the JavaScript part I'm attempting to test:
window.onload = function() {
alert("xx == ", document.getElementById("xml2"));
}
alert("xx2 == ", document.getElementById("xml2"));
alert(JSON.stringify(opensheetmusicdisplay, null, 1));
When I run this, they both instances of "xml2" show blanks. The opensheetmusicdisplay does show data, which means it is reading from the source in the head section in index.html
It was pointed out to me in the comments that alert only take one argument. That's a mistake that I'm going to let sit for the moment. The error in the console is TypeError: document.getElementById(...) is null.
Now, this is the main.js. There are a lot of comments because of my various ideas:
// vue imports and config
import Vue from 'vue'
import App from '#/App'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
Vue.config.productionTip = false
// page imports
import Notation from '#/components/Notation'
import HomePage from '#/components/HomePage'
// component imports and registration
import { FoundationCSS } from '#/../node_modules/foundation-sites/dist/css/foundation.min.css'
Vue.component('foundation-css', FoundationCSS)
import SideNav from '#/components/SideNav'
Vue.component('side-nav', SideNav);
// import * as Osmd from '#/../public/js/osmd/opensheetmusicdisplay.min.js'
// Vue.component('osmd-js', Osmd)
// import { OsmdJs } from '#/components/Osmd'
import * as XmlJs from '#/../public/js/osmd/xml-loader.js'
Vue.component('xml-js', XmlJs)
// import XLoad from '#/components/XmlLoader'
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/',
components: {
maininfo: HomePage
}
},
{ path: '/chromatic-scales/c-chromatic-scale',
components: {
maininfo: Notation// ,
// xmlloader: XLoad
}
}
]
})
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})
I registered XmlJs as global because this is the only way out of 100 things that actually works. I then embed it in Notation.vue like so:
<template>
<div>
<div id="xml">
{{ notation.data }}
</div>
<xml-js />
</div>
</template>
<script>
import axios from 'axios'
export default ({
data () {
return {
notation: null,
}
},
mounted () {
axios
.get('http://localhost:3000/chromatic-scales/c-chromatic-scale')
.then(result => (this.notation = result))
}})
</script>
<style scoped></style>
The last file is the meat and potatoes of what I'm trying to do. The xml-loader.js slurps the data from <div id="xml"> and does whatever magic the program does in order to render the output I want. The issue is that there doesn't seem to be anyway to wait for the stuff in {{ notation.data }}.
I am new to using vuejs and front-end javascript frameworks in general. I do recognize the code is probably not optimal at this time.
There is race condition where DOM element is not available at the time when it's accessed. The solution is to not access DOM elements created by Vue outside of it. DOM element is ready for use only after asynchronous request:
<template>
<div>
<div ref="xml" id="xml">
{{ notation.data }}
</div>
<xml-js />
</div>
</template>
<script>
import axios from 'axios'
export default ({
data () {
return {
notation: null,
}
},
async mounted () {
const result = await axios
.get('http://localhost:3000/chromatic-scales/c-chromatic-scale')
this.notation = result;
this.$nextTick(); // wait for re-render
renderXml(this.$ref.xml); // pass DOM element to third-party renderer
}})
You can import xml-loader.js into the Notation.vue as a function. Then you can simply do something like this:
mounted () {
axios.get(PATH).then(result => {
this.notation = result
let xmlResult = loadXML(result)
doSomethingWithResult(xmlResult)
}
},
methods: {
doSomethingWithResult (result) {
// do something
}
}

Pass variable to vuejs module

I was using the event emitter from vue to make the main app talk to the components and it was working as long as I had the app and all the components in one .js file.
But now I wish to have them separated, one component per file, and obviously I cannot use the event emitter anymore, because the app appears undefined in the modules.
What do I need to change to be able have the app and components communicate again?
My code:
my-app.js
import My from '/my-module.js';
const app = new Vue({
router: new VueRouter({
mode: 'history',
routes: [{
path: '/',
component: My,
}]),
methods: {
created(){
this.ws = new WebSocket(...);
this.ws.onmessage = event => {
this.$emit('message', e);
}
}
}
}).$mount('#app');
my-module.js
export default {
template: `....`
created(){
// I want to be able to access 'app' here
// or another way to receive the messages
console.log(app);
app.$on('message', ...)
}
}
As you can see I only need this because I'm using websockets and the app holds the websocket client. The components need to do something when certain messages are received.
In your case, you may use those events in multiple components, specialy when app is still growing. i think you better use eventbus to emit and catch all of them.
here how to use eventbus in vuejs : https://alligator.io/vuejs/global-event-bus/.
in you're case :
import { eventBus } from 'path/to/file';
...
methods: {
created(){
this.ws = new WebSocket(...);
this.ws.onmessage = event => {
eventBus.$emit('message', e);
}
}
}
Other component :
import { eventBus } from 'path/to/file';
...
created() {
eventBus.$on('message',() => ) {
// code
}
}

How to load all server side data on initial vue.js / vue-router load?

I'm currently making use of the WordPress REST API, and vue-router to transition between pages on a small single page site. However, when I make an AJAX call to the server using the REST API, the data loads, but only after the page has already rendered.
The vue-router documentation provides insight in regards to how to load data before and after navigating to each route, but I'd like to know how to load all route and page data on the initial page load, circumventing the need to load data each time a route is activated.
Note, I'm loading my data into the acf property, and then accessing it within a .vue file component using this.$parent.acfs.
main.js Router Code:
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/tickets', component: Tickets },
{ path: '/sponsors', component: Sponsors },
],
hashbang: false
});
exports.router = router;
const app = new Vue({
router,
data: {
acfs: ''
},
created() {
$.ajax({
url: 'http://localhost/placeholder/wp-json/acf/v2/page/2',
type: 'GET',
success: function(response) {
console.log(response);
this.acfs = response.acf;
// this.backgroundImage = response.acf.background_image.url
}.bind(this)
})
}
}).$mount('#app')
Home.vue Component Code:
export default {
name: 'about',
data () {
return {
acf: this.$parent.acfs,
}
},
}
Any ideas?
My approach is to delay construction of the store and main Vue until my AJAX call has returned.
store.js
import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions';
import getters from './getters';
import mutations from './mutations';
Vue.use(Vuex);
function builder(data) {
return new Vuex.Store({
state: {
exams: data,
},
actions,
getters,
mutations,
});
}
export default builder;
main.js
import Vue from 'vue';
import VueResource from 'vue-resource';
import App from './App';
import router from './router';
import store from './store';
Vue.config.productionTip = false;
Vue.use(VueResource);
Vue.http.options.root = 'https://miguelmartinez.com/api/';
Vue.http.get('data')
.then(response => response.json())
.then((data) => {
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store: store(data),
template: '<App/>',
components: { App },
});
});
I have used this approach with other frameworks such as Angular and ExtJS.
You can use navigation guards.
On a specific component, it would look like this:
export default {
beforeRouteEnter (to, from, next) {
// my ajax call
}
};
You can also add a navigation guard to all components:
router.beforeEach((to, from, next) => {
// my ajax call
});
One thing to remember is that navigation guards are async, so you need to call the next() callback when the data loading is finished. A real example from my app (where the guard function resides in a separate file):
export default function(to, from, next) {
Promise.all([
IngredientTypes.init(),
Units.init(),
MashTypes.init()
]).then(() => {
next();
});
};
In your case, you'd need to call next() in the success callback, of course.
I've comprised my own version based on all the great responses to this post.. and several years having passed by as well giving me more tools.
In main.js, I use async/await to call a prefetch service to load any data that must be there on startup. I find this increases readability. After I get the data comms, I then dispatch it to the appropriate vuex store module in the beforeCreate() hook.
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import { prefetchAppData } from '#/services/prefetch.service';
(async () => {
let comms = await prefetchAppData();
new Vue({
router,
store,
beforeCreate() {
store.dispatch('communityModule/initialize', comms);
},
mounted() {},
render: h => h(App)
}).$mount('#app');
})();
I feel compelled to warn those be careful what you prefetch. Try to do this sparingly as it does delay initial app loading which is not ideal for a good user experience.
Here's my sample prefetch.service.js which does the data load. This of course could be more sophisticated.
import api from '#api/community.api';
export async function prefetchAppData() {
return await api.getCommunities();
}
A simple vue store. This store maintains a list of 'communities' that the app requires to be loaded before application start.
community.store.js (note im using vuex modules)
export const communityModule = {
namespaced: true,
state: {
communities: []
},
getters: {
communities(state) {
return state.communities;
},
},
mutations: {
SET_COMMUNITIES(state, communities) {
state.communities = communities;
}
},
actions: {
// instead of loading data here, it is passed in
initialize({ commit }, comms) {
commit('SET_COMMUNITIES', comms);
}
}
};
Alright, I finally figured this thing out. All I'm doing is calling a synchronous ajax request within my main.js file where my root vue instance is instantiated, and assigning a data property the requested data as so:
main.js
let acfData;
$.ajax({
async: false,
url: 'http://localhost/placeholder/wp-json/acf/v2/page/2',
type: 'GET',
success: function(response) {
console.log(response.acf);
acfData = response.acf;
}.bind(this)
})
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/tickets', component: Tickets },
{ path: '/sponsors', component: Sponsors },
],
hashbang: false
});
exports.router = router;
const app = new Vue({
router,
data: {
acfs: acfData
},
created() {
}
}).$mount('#app')
From here, I can use the pulled data within each individual .vue file / component like so:
export default {
name: 'app',
data () {
return {
acf: this.$parent.acfs,
}
},
Finally, I render the data within the same .vue template with the following:
<template>
<transition
name="home"
v-on:enter="enter"
v-on:leave="leave"
v-bind:css="false"
mode="out-in"
>
<div class="full-height-container background-image home" v-bind:style="{backgroundImage: 'url(' + this.acf.home_background_image.url + ')'}">
<div class="content-container">
<h1 class="white bold home-title">{{ acf.home_title }}</h1>
<h2 class="white home-subtitle">{{ acf.home_subtitle }}</h2>
<div class="button-block">
<button class="white home-button-1">{{ acf.link_title_1 }}</button>
<button class="white home-button-2">{{ acf.link_title_2 }}</button>
</div>
</div>
</div>
</transition>
</template>
The most important piece of information to take away, is that all of the ACF data is only being called ONCE at the very beginning, compared to every time a route is visited using something like beforeRouteEnter (to, from, next). As a result, I'm able to get silky smooth page transitions as desired.
Hope this helps whoever comes across the same problem.
Check this section in docs of Vue Router
https://router.vuejs.org/guide/advanced/data-fetching.html
So first of you have to write method that would fetch data from your endpoint, and then use watcher to watch route.
export default {
watch: {
'$route': 'fetchItems'
},
methods: {
fetchItems() {
// fetch logic
}
}
}
Since you are working with WP Rest API, feel free to check my repo on Github https://github.com/bedakb/vuewp/blob/master/public/app/themes/vuewp/app/views/PostView.vue#L39

Categories