Vue & Laravel: Access and use eventHub - laravel

In resources/assets/js/app.js I have created eventHub Vue instance:
require('./bootstrap');
var eventHub = new Vue();
Vue.component('todos-list', require('./components/todos/TodoList.vue'));
Vue.component('todos-add', require('./components/todos/TodoAdd.vue'));
const app = new Vue({
el: '#app'
});
How can I use it in components that are created in separate .vue files?
For example, I also have two components:
todos-list located in /components/todos/TodoList.vue, which is used to fetch the data from server-side using vue-resource:
<template id="todos-list-template">
<div>
<ul v-if="todos.length > 0">
<li v-for="todo in todos">
{{ todo.title }}
</li>
</ul>
<p v-else>You don't hanve any Todos.</p>
</div>
</template>
<script>
export default {
template: '#todos-list-template',
data() {
return {
todos: {}
}
},
mounted() {
this.$http.get('api/vue/todos').then(function(response) {
this.todos = response.data;
});
},
methods: {
handleAddedTodo: function(new_todo) {
this.todos.push(new_todo);
}
},
created: function() {
eventHub.$on('add', this.handleAddedTodo);
},
destroyed: function() {
eventHub.$off('add', this.handleAddedTodo);
}
}
</script>
todos-add located in /components/todos/TodoAdd.vue which is used to add (save) the new 'todo' using vue-resource:
<template id="todos-add-template">
<div>
<form v-on:submit.prevent="addNewTodo()">
<div class="form-group">
<input v-model="newTodo.title" class="form-control" placeholder="Add a new Todo">
</div>
<div class="form-group">
<button>Add Todo</button>
</div>
</form>
</div>
</template>
<script>
export default {
template: '#todos-add-template',
data() {
return {
newTodo: { id: null, title: this.title, completed: false }
}
},
methods: {
addNewTodo() {
this.$http.post('api/vue/todos', { title: this.newTodo.title }).then(function(response) {
if (response.status == 201) {
eventHub.$emit('add', response.data);
this.newTodo = { id: null, title: this.title, completed: false }
}
}, function(response) {
console.log(response.status + ' - '+ response.statusText);
})
}
}
}
</script>
When I add (save) new 'todo' using the todos-add component (TodoAdd.vue) - I want to update data in todos-list component. If I understood well the documentation - for component communication we need to use centralized event hub. And that's what I tried - but I am getting the following error:
eventHub is not defined
I guess because it is defined in app.js, but how can I use in components that are created in separate .vue files?

You are getting this error because eventHub is actually not defined where you are using it. You have to export this from app.js and import in TodoAdd.vue.
in app.js:
var eventHub = new Vue()
exports.eventHub = eventHub
Add this code in TodoAdd.vue:
<script>
import eventHub from '/path/of/app'
export default {
template: '#todos-list-template',
This should make eventHub availble in TodoAdd.vue.
Edited
As the comment suggests, you may consider using vuex, as you have data whcih is being used across components, and going forward I see chances of getting it more complex, more event handlers and event listerners, which can quickly become a nightmare to manage.

var eventHub = new Vue()
You are getting this error because eventHub is actually not defined where you are using it. You have to export this from app.js file. Use this one
window.eventHub = new Vue()

Related

Vuejs custom Modal event bus is not firing

I have created my own custom Modal plugin in vuejs to be added to my Laravel 8 app. The problem I am facing is opening the modal.
I have created the plugin in my app.js file
const Modal = {
install (Vue) {
this.event = new Vue()
Vue.prototype.$modal = {
show (modal, params = {}) {
Modal.event.$emit('show', modal, params)
},
$event: this.event
}
}
}
Vue.use(Modal)
I have created two vue components for my modal
<!-- AppModal //-->
<template>
<transition name="modal">
<div v-if="visible">
<div class="app-modal" #click.prevent="$modal.hide(name)"></div>
<div class="app-modal-inner">
close
<slot name="body" :params="params"/>
</div>
</div>
</transition>
</template>
<script>
export default {
name: "AppModal",
data () {
return {
params: {},
visible: false,
}
},
props: {
name: {
required: true,
type: String,
}
},
methods: {
setVisible () {
this.visible = true
},
setHidden () {
this.visible = false
}
},
beforeMount() {
this.$modal.$event.$on('show', (modal, params) => {
if (this.name !== modal) {
return
}
this.params = params
this.setVisible()
})
},
}
</script>
<!-- AppNonMemberRegisterModal //-->
<template>
<app-modal name="register">
<template slot="header">
<h1 class="text-lg-left text-4xl border border-b-2">Register Now</h1>
</template>
<template slot="body" slot-scope="{ params }">
<p>You need to register in order to share, comment and like on the site</p>
</template>
</app-modal>
</template>
<script>
import AppModal from "../AppModal";
export default {
name: "AppNonMemberRegisterModal",
components: { AppModal },
}
</script>
Where I am firing the event I import the AppNonMemberRegisterModal and I have the following click event: #click.prevent="$modal.show('register')".
When the following code is reached Modal.event.$emit('show', modal, params) I get the following error messages in my cnosole console.log(Modal)
Vue 3 removes the Event API (i.e., $on, $off, etc.). The migration guide recommends using tiny-emitter to create your own event bus. That example shows how to create a global bus, but it seems your plugin just needs a local bus, which you could create like this:
// eventBus.js
import Emitter from 'tiny-emitter'
export function createEventBus() {
const emitter = new Emitter()
return {
$on: (...args) => emitter.on(...args),
$once: (...args) => emitter.once(...args),
$off: (...args) => emitter.off(...args),
$emit: (...args) => emitter.emit(...args)
}
}
Then in your plugin, create a global with app.config.globalProperties, referring to the locally created event bus:
// myPlugin.js
import { createEventBus } from './eventBus'
export default {
install(app) {
const eventBus = createEventBus()
app.config.globalProperties.$modal = {
show (modal, params = {}) {
eventBus.$emit('show', modal, params)
},
$event: eventBus
}
}
}
And install it:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import myPlugin from './myPlugin'
createApp(App).use(myPlugin).mount('#app')
Also be aware that your slot usage in AppRegisterModal.vue needs to be updated to the latest syntax (v-slot or # shorthand):
<app-modal name="register">
<!--
<template slot="header">
-->
<template #header>
<!--
<template slot="body" slot-scope="{ params }">
-->
<template #body="{ params }">
</app-modal>
demo

Laravel vue.js and vuex link body text by id and show in a new component

I am very new to Laravel and Vuex, I have a simple array of post on my page.
test 1
test 2
test 3
I am trying to link the text on the AppPost.vue component and show the post that has been clicked on a new component (AppShowpost.vue) on the same page. I believe I have to get the post by id and change the state? any help would be good. Thank you.
when you click test 1 it will show "test 1" on a new component (AppShowpost.vue)
In side my store timeline.js, I belive I need to get the post by id and change the state ?
import axios from 'axios'
export default {
namespaced: true,
state: {
posts: []
},
getters: {
posts (state) {
return state.posts
}
},
mutations: {
PUSH_POSTS (state, data) {
state.posts.push(...data)
}
},
actions: {
async getPosts ({ commit }) {
let response = await axios.get('timeline')
commit('PUSH_POSTS', response.data.data)
}
}
}
My AppTimeline.vue component
<template>
<div>
<app-post
v-for="post in posts"
:key="post.id"
:post="post"
/>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapGetters({
posts: 'timeline/posts'
})
},
methods: {
...mapActions({
getPosts: 'timeline/getPosts'
})
},
mounted () {
this.getPosts()
}
}
</script>
My AppPost.vue component. I need to link the post.body to display the post in my AppShowpost.vue component.
<template>
<div class="w-full inline-block p-4">
<div class="flex w-full">
<p>
{{ post.body }}
</p>
</div>
</div>
</template>
<script>
export default {
props: {
post: {
required: true,
type: Object
}
}
}
</script>
My AppSowpost.vue component that needs to display the post that is clicked.
<template>
<div>
// Displaypost ?
</div>
</template>
<script>
export default {
// Get post from id ?
}
</script>
Okay you can create a new state in your vuex "current_state", asyou said, you can dispatch a mutation by passing the id to the vuex.
state: {
posts: [],
current_state_id : null
},
In your mutations
set_state_id (state, data) {
state.current_state_id = data;
}
On your app post.vue, you can set a computed property that watches the current state
computed: {
currentState() {
return this.$store.getters["timeline/current_state_id"];
}}
And create a watcher for the computed property to display the current id/post
watch: {
currentState: function(val) {
console.log(val);
},
Maybe this will help you. First I will recommend to use router-link. Read about router link here if your interested. It is very helpful and easy to use. But you will have to define the url and pass parameter on our vue-route(see bellow).
1.You can wrap your post.body in router-link as follow.
//With this approach, you don't need any function in methods
<router-link :to="'/posts/show/' + post.id">
{{ post.body }}
</router-link>
2. In your AppSowpost.vue component, you can find the post in vuex state based on url params as follow.
<template>
<div> {{ thisPost.body }} </div>
</template>
// ****************
computed: {
...mapState({ posts: state=>state.posts }),
// Let's get our single post with the help of url parameter passed on our url
thisPost() { return this.posts.find(p => p.id == this.$route.params.id) || {}; }
},
mounted() { this.$store.dispatch("getPosts");}
3. Let's define our vue route.
path: "posts/show/:id",
name: "showpost",
params: true, // Make sure the params is set to true
component: () => import("#/Components/AppShowPost.vue"),
Your Mutations should look as simple as this.
mutations: {
PUSH_POSTS (state, data) {
state.posts = data;
}
},
Please let me know how it goes.

Update and access alert message variable between Vue components

I am using VueJS with Laravel 6.0. What I'm trying to achieve is that to create global variables alertStatus and alertMsg, so that every time when an AJAX call is made, the global variables can be updated to display an alert message to user.
So I decided to use prototype variable for this case. The idea is that when AJAX call is success/fail in User.vue, the prototype variable should be updated, and Alerts.vue should display it accordingly.
However, it seems that the prototype variable display does not update when the data is changed in User.vue component.
I'm not sure if my methods are correct, would like to get some ideas from stackoverflow.
Thanks
main.js
Vue.prototype.$alertStatus = '';
Vue.prototype.$alertMsg = [];
Alerts.vue
<template>
<div class="alert alert-light alert-elevate" role="alert">
<div class="alert-icon">
<i class="flaticon-warning kt-font-brand"></i>
</div>
<div class="alert-text">
{{alertMsg}}
</div>
</div>
</template>
User.vue
<script>
export default {
mounted() {
var datatable = this.init();
datatable.on('kt-datatable--on-ajax-fail', function(event, data){
this.$alertStatus = data.responseJSON.status;
this.$alertMsg = data.responseJSON.msg;
});
},
}
</script>
I would consider using an event handler instead of the global prototype.
Event.js — credit to Jeffrey Way of https://laracasts.com/
class Event {
constructor() {
this.vue = new Vue();
}
fire(event, data = null) {
this.vue.$emit(event, data);
}
listen(event, callback) {
this.vue.$on(event, callback);
}
}
export default Event;
I have outlined the basic usage below, plus I added a v-if to your alert to hide it when not in use.
app.js
import Event from './Event';
window.Event = new Event;
Alerts.vue
<template>
<div v-if="show" class="alert alert-light alert-elevate" role="alert">
<div class="alert-icon"><i class="flaticon-warning kt-font-brand"></i></div>
<div class="alert-text">
{{ alert.msg }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
alert: {},
show: false,
}
},
mounted() {
// listen for a global event
Event.listen('show-alert',alert => {
this.alert = alert;
this.show = true;
});
},
}
</script>
User.vue
<script>
export default {
mounted() {
var datatable = this.init();
datatable.on('kt-datatable--on-ajax-fail', function(event, data){
// fire a global event
Event.fire('show-alert',{
status: data.responseJSON.status,
msg: data.responseJSON.msg,
});
});
},
}
</script>

How to transfer data between components placed in different views

I have two compoennts placed in one the same view.
#extends('layouts.app')
#section('content')
<bus></bus>
<bus2></bus2>
#endsection
I want to pass data (name) from one component to other one after clicking button. To do that I' using $emit function.
/// bus component
<template>
<div>
<p> Name Bus 1{{name}}</p>
<input type="button" #click="setName()" value="s"/>
</div>
</template>
<script>
export default {
created() {},
data: function() {
return {
name: "Volvo"
};
},
methods: {
setName: function(id) {
this.$root.$emit("sname", this.name);
}
},
props: []
};
</script>
///bus 2 component
<template>
<div>
<p> Name bus 2{{name}}</p>
</div>
</template>
<script>
export default {
created() {
this.$root.$on("sname", data => {
this.name = data;
});
},
data: function() {
return {
count: 0,
name: ""
};
},
methods: {}
};
</script>
Everything works fine. Name is transfered from bus to bus2. The problem exists when I place bus2 in different view - data are not transfered but code is the same. How can I transfer data between components placed in different views
Try using Vuex to specify your app state, and mutate this when it's necessary.
Vuex states are accessible from every components using this.$store or $store.

Property or method not defined on instance

I'm trying to create a watcher to bind the value from an input run it through a method and display that value on the page. Its a fairly straightforward sample of code I'm working with but I believe I have a scoping issue but I don't see anything wrong.
I tried changing a few things such as the convertTitle function scoping. e.g. But I got the same error
convertTitle() {
return Slug(this.title)
}
My Vue Component -
<template>
<div class="slug-widget">
<span class="root-url">{{url}}</span>
<span class="slug" v-if="slug !== nil">{{slug}}</span>
</div>
</template>
<script>
export default {
props: {
url: {
type: String,
required: true
},
title: {
type: String,
required: true
}
},
data: function() {
return {
slug: this.convertTitle()
}
},
methods: {
convertTitle: function() {
return Slug(this.title)
}
},
watch: {
title: function(val) {
this.slug = this.convertTitle();
}
}
}
</script>
My Blade Partial with input -
#extends('admin.admin')
#section('content')
<div class="row">
<div class="col-md-9">
<div class="row">
<h1>Create new page</h1>
</div>
<div class="row">
<div class="pcard">
<form action="" method="POST" class="">
{{ csrf_field() }}
<label>Title</label>
<input type="text" name="page-title" placeholder="" v-model="title">
<slug-widget url="{{url('/')}}" :title="title"></slug-widget>
</form>
</div>
</div>
</div>
</div>
#endsection
#section('scripts')
<script>
var app = new Vue({
el: '#app',
data: {
title: ''
}
});
</script>
#endsection
Full error Trace -
[Vue warn]: Property or method "title" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
(found in <Root>)
Updated with compute -
export default {
props: {
url: {
type: String,
required: true
},
title: {
type: String,
required: true
}
},
computed: {
slug() {
return Slug(this.title)
}
}
}
The error was resolved by removing the following snippet from app.js
const app = new Vue({
el: '#app'
});
You shouldn't have, and shouldn't ever need, a function call to supply the value of something in your data.
It sounds like you just need to put convertTitle() into a computed and away you go?

Resources