Update and access alert message variable between Vue components - laravel

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>

Related

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.

Vuejs Displaying a component multiple times with Axios in it

I have been trying to figure out this for a hours but no luck. I have 2 components. The first component is dynamic and the second component just gets the user geolocation. The geolocation is then displayed in the first component.
My problem is that I display the first component a few times on the page and every time it is displayed it makes a GET request which is inefficient . If I display the component 3 times it will make 3 GET requests.
What would be the best way to rewrite this?
Thanks for the help
Component 1:
<template>
<section id="under-info">
THe user is from <ip_info></ip_info>
</section>
</template>
<script>
export default {
}
</script>
Component 2:
<template>
<span id="user-city">
{{value}}
</span>
</template>
<script>
export default {
mounted: function () {
this.$nextTick(function () {
this.getIpInfo(this.param)
})
},
props:['param'],
data: function () {
return {
value:null
}
},
methods:{
getIpInfo(){
var vm = this
delete axios.defaults.headers.common["X-Requested-With"];
delete axios.defaults.headers.common["X-CSRF-TOKEN"];
axios
.get('http://api.ipstack.com/check?access_key=?',{
timeout: 1000
})
.then(function(response) {
vm.value = response.data['city]
})
}
},
}
</script>
Wrap the code where you want to reuse this value 3x in a component.
<template>
<div>
The user is from {{location}}, and {{location}} is a great place. They have a baseball team in {{location}}
</div>
</template>
<script>
export default {
mounted: function () {
this.$nextTick(function () {
if(this.needLocation){
this.getIpInfo(this.param)
}
})
},
props:['param', 'needLocation'],
data: function () {
return {
location:null
}
},
methods:{
getIpInfo(){
this.location = //results from your API call
}
}
</script>

Vue Component with Stripe JS v3

I am using Vue component for my checkout form.
The stripe js (v3) file was included in the header section.
The form was in Component
This component has two section. One is to get payment details from the user and another is to submit card details.
<template>
<div class="payment_form">
<div id="payment_details" v-if="showPaymentDetails">
<!-- User input goes here. Like username phone email -->
</div>
<div id="stripe-form" v-if="showStripeForm">
<form action="/charge" method="post" id="payment-form" #submit.prevent="createStripeToken()">
<div class="form-row">
<label for="card-element">
Credit or debit card
</label>
<div id="card-element">
<!-- a Stripe Element will be inserted here. -->
</div>
<!-- Used to display Element errors -->
<div id="card-errors" role="alert"></div>
</div>
<button>Submit Payment</button>
</form>
</div>
</div>
</template>
<script>
import { Validator } from 'vee-validate';
export default {
data() {
return {
stripeToken: '',
showPaymentDetails: true,
showStripeForm: true,
}
},
created() {
},
methods: {
validateForm() {
self = this;
this.$validator.validateAll().then(result => {
if (result) {
// eslint-disable-next-line
alert('From Submitted!');
console.log(this.$data);
axios.post('/data',{
name:this.name,
})
.then(function (response) {
self.showStripeForm = true;
console.log(response);
})
.catch(function (error) {
console.log(error);
});
return;
}
});
},
createStripeToken(){
var form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {
event.preventDefault();
window.stripe.createToken(card).then(function(result) {
if (result.error) {
// Inform the user if there was an error
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// Send the token to your server
console.log(result.token);
}
});
});
},
initStripe(){
window.stripe = Stripe('stripe_test_key_here');
var elements = stripe.elements();
var style = {
base: {
// Add your base input styles here. For example:
fontSize: '16px',
lineHeight: '24px'
}
};
// Create an instance of the card Element
window.card = elements.create('card', {style: style});
// Add an instance of the card Element into the `card-element` <div>
window.card.mount('#card-element');
}
},
mounted() {
this.initStripe();
setTimeout(function () {
this.showStripeForm = false;
},2000);
}
}
</script>
I try to load the stripe form on page load and try to disable the element via showStripeForm.
But vue unset the loaded stripe card form from the stripe server and saved the dom to its original state.
So i can't trigger the stripe form on the axios callback.
I don't want to user stripe checkout and stripe js v1(getting input on your own form is deprecated after this version).
In mounted. Change the setTimeout callback to an arrow function, otherwise, this will point to Window instead of Vue.
mounted() {
setTimeout(() => {
this.showStripeForm = false
}, 2000)
}
Also, the way you access the DOM is not so Vue-ish. You could use ref on the DOM element you want to use in your code. For example:
<form action="/charge" method="post" ref="payment-form" #submit.prevent="createStripeToken()">
Then access it from $refs like this:
var form = this.$refs['payment-form']
/*
Same result as document.getElementById('payment-form')
but without using an id attribute.
*/

Vue & Laravel: Access and use eventHub

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()

Meteor: Error: {{#each}} currently only accepts arrays, cursors or falsey values

I keep getting this error message when I click on the send button. Im trying to create a Instant Messenger app where online users can chat one on one. I am a beginner and I would really appreciate any help. Here is my error message, again it appears in the console once I click the Send button.
Exception from Tracker recompute function: meteor.js:862 Error:
{{#each}} currently only accepts arrays, cursors or falsey values.
at badSequenceError (observe-sequence.js:148)
at observe-sequence.js:113
at Object.Tracker.nonreactive (tracker.js:597)
at observe-sequence.js:90
at Tracker.Computation._compute (tracker.js:331)
at Tracker.Computation._recompute (tracker.js:350)
at Object.Tracker._runFlush (tracker.js:489)
at onGlobalMessage (meteor.js:347)
Here is my HTML
<template name="chat_page">
<h2>Type in the box below to send a message!</h2>
<div class="row">
<div class="col-md-12">
<div class="well well-lg">
{{#each messages}}
{{> chat_message}}
{{/each}}
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form class="js-send-chat">
<input class="input" type="text" name="chat" placeholder="type a message here...">
<input type="submit" value="Send">
</form>
</div>
</div>
</template>
<!-- simple template that displays a message -->
<template name="chat_message">
<div class = "container">
<div class = "row">
<img src="/{{profile.avatar}}" class="avatar_img">
{{username}} said: {{text}}
</div>
</div>
<br>
</template>
Client Side
Template.chat_page.helpers({
messages: function () {
var chat = Chats.findOne({ _id: Session.get("chatId") });
return chat.messages;
},
other_user: function () {
return "";
}
});
Template.chat_page.events({
'submit .js-send-chat': function (event) {
console.log(event);
event.preventDefault();
var chat = Chats.findOne({ _id: Session.get("chatId") });
if (chat) {
var msgs = chat.messages;
if (! msgs) {
msgs = [];
}
msgs.push({ text: event.target.chat.value });
event.target.chat.value = "";
chat.messages = msgs;
Chats.update({ _id: chat._id }, { $set : { messages: chat } });
Meteor.call("sendMessage", chat);
}
}
});
Parts of the server side
Meteor.publish("chats", function () {
return Chats.find();
});
Meteor.publish("userStatus", function () {
return Meteor.users.find({ "status.online": true });
});
Meteor.publish("userData", function () {
if (this.userId) {
return Meteor.users.find({ _id: this.userId },{ fields: { 'other': 1, 'things': 1 } });
} else {
this.ready();
}
return Meteor.users.find({ "status.online": true });
});
Meteor.publish("users", function () {
return Meteor.users.find({ "status.online": true });
});
Chats.allow({
insert: function () { return true; },
update: function () { return true; },
remove: function () { return true; }
});
Meteor.methods({
sendMessage: function (chat) {
Chats.insert({
chat: chat,
createdAt: new Date(),
username: Meteor.user().profile.username,
avatar: Meteor.user().profile.avatar,
});
}
});
Chances are your subscriptions aren't ready. This means that Chats.findOne() will return nothing, meaning that Chats.findOne().messages will be undefined.
Try the following:
{{ #if Template.subscriptionsReady }}
{{#each messages}}
{{/each}}
{{/else}}
Alternatively, use a find() on chats, then {{#each}} on the messages within that chat. For example:
Template['Chat'].helpers({
chats: function () {
return Chats.find(Session.get('chatId')); // _id is unique, so this should only ever have one result.
}
});
Then in template:
{{#each chats}}
{{#each messages}}
{{>chat_message}}
{{/each}}
{{/each}}
I think there might be a logical error in this line
Chats.update({ _id : chat._id }, { $set : { messages : chat } });
You are setting the value of the field messages to chat. But chat is an object. So in your helper when you are returning Chats.findOne().messages to the {{#each}} block, you are actually returning an object which is not a valid value to be sent to an {{#each}} block and hence the error.
I think what you mean to do is
Chats.update({ _id : chat._id }, { $set : { messages : msgs } });

Resources