Call method when modal closes in Vue - vue.js

I have a Vue app (and I'm relatively new to Vue), anyway I have a generic error modal which is displayed when any of my axios calls fail.
On the modal, I want to be able to retry the failed process when the 'Retry' button is clicked but I'm struggling a bit on how to achieve this. I don't think props will help me as the modal is triggered by
VueEvent.$emit('show-error-modal')
I have managed in my catch to pass the function which has failed by using
VueEvent.$emit('show-error-modal', (this.test));
Then in my modal, I have access to it using
created() {
VueEvent.$on('show-error-modal', (processFailed) => {
console.log('processFailed', processFailed)
this.processFailed = processFailed;
$('#errorModal').modal('show').on('shown.bs.modal', this.focus);
});
}
Using 'F12' it gives
test: function test() {
var _this2 = this;
var page = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
alert('boo');
this.loading = true;
var order_by = {
'ho_manufacturers.name': 4,
'services.servicename': 4
};
axios.post('/test/url', {
id: this.selectedManufacturers,
page: page,
order_by: order_by
}).then(function (response) {
var paginator = response.data.paginator;
_this2.$store.dispatch('paginateProducts', paginator);
_this2.$store.dispatch('setProductPaginator', paginator);
_this2.loading = false;
})["catch"](function (error) {
var processFailed = 'fetchProducts';
_this2.loading = false;
VueEvent.$emit('show-error-modal', _this2.test);
console.log(error);
});
},
I don't think this is the right way of doing it though as all this. are replaced with _this2 as shown above. I need the modal to be generic so I can reuse it but I can't figure it out how to retry the process when clicking the button.
The modals are registered in my app.vue file as
<template>
<div>
<!-- Other code here -->
<app-error-modal />
</div>
</template>
<script>
// Other code here
import ErrorModal from './components/ErrorModal';
export default {
name: 'App',
components: {
// Other code here
appErrorModal: ErrorModal
}
// Other code here
</script>
Modal button HTML
<button ref="retryButton" class="btn btn-success col-lg-2" type="submit" data-dismiss="modal" #click="retryProcess">Retry</button>
Modal script code
<script>
export default {
name: "ErrorModal",
created() {
VueEvent.$on('show-error-modal', (processFailed) => {
console.log('processFailed', processFailed)
this.processFailed = processFailed;
$('#errorModal').modal('show').on('shown.bs.modal', this.focus);
});
},
methods: {
focus() {
this.$refs.retryButton.focus();
},
retryProcess() {
//this.$parent.test(); TRIED THIS BUT DIDN'T WORK
}
}
}
</script>
I'd rather not have to the store.

Use custom event on your component
<div id="app">
<error-modal v-if="isError" v-on:retry-clicked="retry"></error-modal>
<button #click="isError = true">Make Error</button>
</div>
const errorModal = {
template : "<button #click=\"$emit('retry-clicked')\">Retry</button>"
}
new Vue({
el : "#app",
data : {
isError : false
},
components : {
errorModal
},
methods : {
retry : function(){
this.isError = false;
console.log("child component has called parent when retry clicked")
}
}
})
Custom event on component - VUEJS DOC

Everything you have there looks correct. You are passing the function to retry ("test") as "processFailed" - I would call that something different such as retryFn.
Then in your error modal component you just need:
<button #click="processFailed">Retry</button>
Don't worry about what the browser shows you in F12, that is the transpiled Javascript, it will work fine.

Related

Vuejs $emit doesnt work in some part of a function, and in other part works

I am using vuejs3 and trying to emit event from a child component.
child Component
<input type="button" v-if="edition_mode" #click="cancel()" class="btn btn-primary" value="Annuler">
[...]
cancel(){
if(this.new_sav){
this.$emit('test')
}else{
console.log('else')
this.$emit('test')
}
},
Parent Component
<div v-if="creation_form">
<h4>Ajout Nouveau Sav</h4>
<sav-form
:initial_data="null"
:li_product="li_product"
:new_sav="true"
:customer_id="data.customer.id"
#action="form_action"
#test="test()"/>
</div>
[...]
test(){
console.log('test emit works')
}
When cancel() is executed, in the if case $emit() works correctly, but in the else case, only 'else' is printed and $emit is not executed. What I am doing wrong here ?
I also have several buttons in child component, in the same div, that all call differents function but some function 'can' emit event and other can't.
I am not sure what issue you are facing but it is working fine in the below code snippet. Please have a look and let me know if any further clarification/discussion required.
Demo :
Vue.component('child', {
template: '<div><button #click="cancel()">Trigger emit event from child!</button></div>',
data() {
return {
isValid: true
}
},
methods: {
cancel: function() {
this.isValid = !this.isValid;
if (this.isValid) {
console.log('valid');
this.$emit('test');
} else {
console.log('not valid');
this.$emit('test')
}
}
}
});
var vm = new Vue({
el: '#app',
methods: {
getTestEvent() {
console.log('Emit event from child triggered!');
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<child #test="getTestEvent"></child>
</div>
I was facing the same issue.
I use Vuex to store authenticated status of the user and my mistake was to use v-if="isAuth" attribute in parent component and the child component set isAuth to false through the store so this.$emit() doesn't work anymore.
Parent Component:
<login-form #onLogin="onLoginHandler" v-if="!isAuth" />
Child Component:
methods: {
example() {
[...]
const response = await axios.post("/login", data);
if (response.status === 200) {
// under the hood isAuth is set to True;
this.$store.dispatch("SET_AUTH", true);
}
// Doesn't work anymore because the parent component v-if is now False.
this.$emit("onLogin", response.data);
}
}

How can I add a click event to a class in Vue **without** changing the html? [duplicate]

Is there a way to add a click handler inside a Vue instance without writing on the markup? I'm loading an SVG via ajax, and I'd like to use Vue click events on it.
My Vue file looks like this:
<template>
<div>
<div class="map" v-html="map"></div>
</div>
</template>
<script>
export default{
data : function(){
return {
map : 'Loading'
};
},
methods : {
getMap : function(){
var thisMap = this;
axios.get('/img/world-map.svg').then(function (response) {
thisMap.map = response.data;
thisMap.loading = false;
}).catch(function (error) {
thisMap.loading = false;
console.log(error);
});
},
},
mounted : function(){
console.log('WorldMap mounted');
this.getMap();
}
}
</script>
The issue is that I'm loading a rather large SVG straight into the page. In a perfect world I would just do something like this:
<template>
<div>
<div class="map" >
<svg>
<g #click="groupClicked" class="asia" id="asia" xmlns:svg="http://www.w3.org/2000/svg">
<path stroke="#FFFFFF" d="M715.817,47.266c-4.45,1.25-8.903,2.497-13.357,3.739c-1.074,0.327-8.403,1.757-5.678,3.204
c-1.922,2.104-2.993,1.568-5.547,1.536c-1.547,1.333,0.981,1.22-0.558,2.421c-0.976,0.761-0.946,1.257-2.106,0.827
c-0.368-0.136-2.223-0.261-1.543,0.759c2.082,1.3,0.231,3.046-1.466,4.011c-1.831-0.38-3.271-1.611-5.245-1.293
c-1.229,0.196-2.104,0.763-3.176-0.205c-1.265-1.143,0.371-1.409,1.378-2.177c1.529-1.168,5.473-0.2,2.834-2.668
c1.061-0.979,2.07-0.946,3.206-1.736c-0.297-0.416-0.649-0.773-1.067-1.068c1.047-1.075,1.679-3.036,3.497-2.725
c1.441,0.249,2.046-1.318,3.182-2.137c1.121-0.811,2.4-1.266,3.771-1.402c1.656-0.165,3.271,0.134,4.347-1.427
c0.921-1.334,1.921-1.218,3.468-0.757c1.687,0.504,2.808-0.159,4.442-0.698c2.313-0.118,4.489-0.946,6.812-1.068
c1.043-1.941,2.354-2.07,4.375-2.331c0.653-0.085,6.433-0.678,4.774,1.792C721.041,46.198,718.024,46.605,715.817,47.266
C711.364,48.516,718.356,46.505,715.817,47.266z"/>
</g>
</svg>
</div>
</div>
</template>
However the SVG I'm loading is about 300kb big and I don't want to be carrying that around with all my JavaScript on every page load.
Any comments or solutions welcome.
Edit
Since asking I've actually got quite far with this approach, which isn't perfect but at the moment it seems pretty good.
var vueBus = new Vue({});
$('body').on('click', 'svg g', function(){
var name = $(this).attr('data-name');
vueBus.$emit('svgGroupClicked', name);
});
and then adding a listener in my .vue file
mounted() : function(){
vueBus.$on('svgGroupClicked', function(){ ... });
}
You can just add a click event via plain javascript after the GET request succeeds:
axios.get('/img/world-map.svg').then(function (response) {
thisMap.map = response.data;
thisMap.addClickHandler();
thisMap.loading = false;
})
Your addClickHandler method would look something like this:
methods: {
addClickHandler: function() {
var gEl = this.$el.getElementsByTagName('g')[0];
gEl.addEventListener('click', function() {
alert('I got clicked');
});
},
}
Or, if you're using jQuery:
methods: {
addClickHandler: function() {
$(this.$el).find('g')[0].click(function() {
alert('I got clicked');
});
},
}

Can a vue component know if a listener is listening?

Say I have a modal dialogue as a Vue component. Sometimes I want OK and Cancel. Sometimes, I just want OK. The cleanest way I can think to do this would be for my component to only display Cancel when it's caller is listening for cancel events. Can this be done?
jsfiddle
markup
<div id="vue-root">
<confirm-modal v-on:ok="handleOk"></confirm-modal>
<confirm-modal v-on:ok="handleOk" v-on:cancel="handleCancel"></confirm-modal>
</div>
code
Vue.component('confirm-modal',{
template : `
<div class="confirm-modal">
Are you sure<br>
<button v-on:click="$emit('ok',{})">OK</button>
<button v-if="'HOW DO I TEST IF CANCEL WILL BE CAPTURED???'" v-on:click="$emit('cancel',{})">Cancel</button
</div>
`,
})
vm = new Vue({
el : '#vue-root',
methods : {
handleOk : function(){
alert('OK already');
},
handleCancel : function(){
}
}
})
First you can emit an event without value and the parent will catch it. You dont need this empty Object.
What i understood from your question is this:
If you to track in confirm-modal component if the cancel button is clicked ?
Then do this.
Vue.component('confirm-modal',{
template : `
<div class="confirm-modal">
Are you sure<br>
<button v-on:click="$emit('ok',{})">OK</button>
<button v-if="isCaptured" v-on:click="cancelClick">Cancel</button
</div>
`,
data: function () {
return {
isCaptured: false,
};
},
methods: {
cancelClick: function() {
this.isCaptured = true;
// or this.$emit('cancel'); then pass prop from parent
},
}
})
vm = new Vue({
el : '#vue-root',
methods : {
handleOk : function(){
alert('OK already');
},
handleCancel : function(){
}
}
})
The easy way to do this, you can pass props from parent to child component when you want to show ok and cancel button.
Here you have some more about props
https://v2.vuejs.org/v2/guide/components.html#Passing-Data-with-Props

Vue.js - Keep Alive Component - Error next Tick

Description
I'm trying to take advantage of the keep-alive functionality of vue-js 2.3 so my AJAX call is made only once.
Problem
The second time I try to open the popup component I get this error :
Error in nextTick: "TypeError: Cannot read property 'insert' of undefined"
TypeError: Cannot read property 'insert' of undefined
Steps
Click on the button to display the popup
Wait for one second
Close the popup
Click again on the button
https://jsfiddle.net/4fwphqhv/
Minimal reproduction example
<div id="app">
<button #click="showDialog = true">Show Component PopUp</button>
<keep-alive>
<popup v-if="showDialog" :show-dialog.sync="showDialog"></popup>
</keep-alive>
</div>
<template id="popup">
<el-dialog :visible.sync="show" #visible-change="updateShowDialog">{{asyncData}}</el-dialog>
</template>
Vue.component('popup', {
template: '#popup',
props : ['showDialog'],
data(){
return {
show: this.showDialog,
asyncData: "Loading please wait"
}
},
methods: {
updateShowDialog(isVisible) {
if (isVisible) return false;
this.$emit('update:showDialog', false )
}
},
created:function (){
const _this = this
setTimeout(() => _this.asyncData = 'Async Data was loaded' , 1000)
},
});
var vm = new Vue({
el: '#app',
data: {
showDialog: false,
},
});
Real code of the popup component
<template>
<el-dialog title="Order in progress" size="large" :visible.sync="show" #visible-change="updateShowLoadOrder"></el-dialog>
</template>
<script>
let popUpData;
export default {
name: '',
data () {
return {
ordersInProgress: [],
show: this.showLoadOrder
}
},
props: ['showLoadOrder'],
methods: {
updateShowLoadOrder (isVisible) {
if (isVisible) return false;
this.$emit('update:showLoadOrder', false)
}
},
created () {
const _this = this;
if (!popUpData) {
axios.get('api/mtm/apiGetOrdersInProgress').then((response) => {
_this.ordersInProgress = popUpData = response.data;
});
} else {
this.ordersInProgress = popUpData;
}
}
}
</script>
Ok. So your problem here is the wrong life-cycle hook.
If you change created to activated... it should work. It did for me in your JS fiddle.
activated:function (){
setTimeout(() => this.asyncData = 'Async Data was loaded' , 1000)
}
There are two other hooks, activated and deactivated. These are for keep-alive components, a topic that is outside the scope of this article. Suffice it to say that they allow you to detect when a component that is wrapped in a tag is toggled on or off. You might use them to fetch data for your component or handle state changes, effectively behaving as created and beforeDestroy without the need to do a full component rebuild.
SOURCE: here

VueJS - trigger Modal from materializecss

I am trying to trigger a modal from the materializecss framework within a VueJS-instance.
Both, VueJS and Materializecss, are implemented correct. On their own both frameworks work fine.
Clicking the open-button results in an error:
Uncaught TypeError: data[option] is not a function
at HTMLDivElement. (adminarea.js:24562)
at Function.each (adminarea.js:10567)
at jQuery.fn.init.each (adminarea.js:10356)
at jQuery.fn.init.Plugin [as modal] (adminarea.js:24556)
at Vue$3.showLoader (adminarea.js:21396)
at boundFn (adminarea.js:54956)
at HTMLButtonElement.invoker (adminarea.js:56467)
This is my Vue-Instance:
const app = new Vue({
el: '#app',
data: {
activeUser: {
username: '',
email: ''
},
},
methods: {
showLoader(){
$('#loaderModal').modal('open');
},
closeLoader(){
$('#loaderModal').modal('close');
}
},
mounted() {
// Get current User
axios.get('/api/currentUser')
.then(response => {
this.activeUser.username = response.data.username;
this.activeUser.email = response.data.email;
});
},
components: {
Admindashboard
}
});
And here is the part of my html-file with the modal structure:
<!-- Modal Structure -->
<div id="loaderModal" class="modal">
<div class="modal-content">
<h4>Fetching data..</h4>
<div class="progress">
<div class="indeterminate"></div>
</div>
</div>
</div>
<button class="btn cyan waves-effect waves-cyan" v-on:click="showLoader">Open</button>
Any ideas? Thanks!
It seems I found an solution:
Nice to know for Laravel-users: for my current project I use Laravel 5.5 with Materializecss, VueJS and VueRouter but I think the solution is universal. Materializecss was installed via npm and has to be included into your application. I've required the css-framework within my ressources/assets/js/bootstrap.js:
...// more code
try {
window.$ = window.jQuery = require('jquery');
window.materialize = require('materialize-css');
} catch (e) {
console.log(e);
}
...// more code
Now you have to initialize the Modal-function on the mounted-event of your wrapping Vue-instance:
const app = new Vue({
router,
data: {
...
},
methods: {
testClick: function(){
console.log('Testklick-Call');
$('#modal1').modal('open');
}
},
mounted: function(){
console.log('Instance mounted');
$('.modal').modal();
}
}).$mount('#app');
The code above is placed within my ressources/assets/js/app.js and is packed by default by Laravel Mix but I think this is universal and also usable without Laravel Mix/Webpack etc.
Now you can call every modal programmatically from where ever you want. I've tested it in my main instance on a click-event. Function is placed in my Vue-instance (see above). HTML-Code see below:
<button v-on:click="testClick">Open Modal</button>
But you can also make use of the modal within a mounted-function or any other function of any component:
<template>
<div>
<p>I am an component!</p>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted!');
$('#modal1').modal('open');
}
}
</script>
This also works, if the component becomes only visible after clicked on a link (using VueRouter).
Hopefully this helps someone except me :)
As suggested here, you need to add following code in the mounted block:
mounted() {
$('#loaderModal').modal(); //New line to be added
// Get current User
axios.get('/api/currentUser')
.then(response => {
this.activeUser.username = response.data.username;
this.activeUser.email = response.data.email;
});
},