How can I use Vuex in Sweetalert2 modal? - vue.js

I'm using sweetalert2 with a vue component injected in the html template of modal. How can I access the vuex inside my component?
Vue.prototype.$myComponentDialog = function (title, propsData) {
let swalOptions = {
title: title,
showCloseButton: true,
showConfirmButton: false,
html:
'<div id="component-container"></div>',
onBeforeOpen: () => {
let ComponentClass = Vue.extend(MyComponent);
let instance = new ComponentClass({
propsData: propsData
});
instance.$mount();
document.getElementById('component-container').appendChild(instance.$el);
}
};
return Vue.swal.mixin({
customClass: {
container: cssClass
},
position: 'center'
}).fire(swalOptions);
};

Related

How do i update my mapbox markers when the geojson data is passed in as props in a vue component

Edit 1
Here is the source code on git
I am working on an Ionic-Vue project with Mapbox integration. My issue is that I am unable to update my geojson data source to create markers on the map, the geoJson data is passed in as props. Here is the flow
On APP.vue created hook
Using capacitor gets user current location.
.then() make an Axios call to the server to fetch the JSON data
On success set the data to store via mutation
On Home.vue[parent component for TheMap.vue]
Computed value called getGeoJson which calls the getters to get geojson data from the state saved an earlier step
Bind the :marker prop with computed value getGeoJson data on the TheMap component to be sent as a prop.
Listen to the event $updatedLocation and call the API action to fetch new geojson data.
On TheMap.vue
On create Hook call the createMap().
In Method: createMap()
Get the user coords again using capacitorJS
init the mapbox map and save it to this.map variable
add Attribution using this.map.addControl
this.map.on("load",cb) inside of cb call .addSource() & then addMarkers()
Create a new marker for the current user and save it to cosnt UserLocationMarker
UserLocationMarker.on("dragend",CB) to emit a event with latest current user location
Here is the code for the same just putting the script tags rather than the whole.vue file
APP.Vue
export default {
name: "App",
data() {
return {
currentLocation: "",
};
},
created() {
Geolocation.getCurrentPosition()
.then((result) => {
this.currentLocation = [
result.coords.longitude,
result.coords.latitude,
];
this.$store.dispatch(
`lists/${POST_GEOJSON_ACTION}`,
this.currentLocation
);
})
.catch((err) => console.log(err));
},
};
Home.vue
<template>
<the-map :markers="geojson" #updatedLocation="updateMap"></the-map>
</template>
<script>
export default {
name: "Home",
computed: {
getGeoJson() {
return this.$store.getters[`lists/${GET_GEOJSON_GETTER}`];
},
},
methods: {
updateMap(center) {
this.$store.dispatch(`lists/${POST_GEOJSON_ACTION}`, center);
},
},
};
</script>
TheMap.vue
export default {
props: ["markers"],
emits: ["updatedLocation"],
data() {
return {
access_token: process.env.VUE_APP_MAP_ACCESS_TOKEN,
center: [0, 0],
map: {},
};
},
mounted() {
this.createMap();
},
methods: {
async createMap() {
try {
const coords = await Geolocation.getCurrentPosition();
this.center = [coords.coords.longitude, coords.coords.latitude];
mapboxgl.accessToken = this.access_token;
//Map Instance
this.map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/userName/API_KEY",
center: this.center,
zoom: 12,
scrollZoom: true,
});
// Custom Attribution over the map for Branding
this.map.addControl(
new mapboxgl.AttributionControl({
customAttribution: ` © Comapny Name`,
})
);
this.map.on("load", function(e) {
this.map.addSource("places", {
type: "geojson",
data: this.markers,
});
this.addMarkers();
});
const UserLocationMarker = new mapboxgl.Marker({
draggable: true,
})
.setLngLat(this.center)
.addTo(this.map);
UserLocationMarker.on("dragend", async (e) => {
this.center = Object.values(e.target.getLngLat());
this.$emit("updatedLocation", this.center);
});
} catch (err) {
console.log(err);
}
},
addMarkers() {
this.markers.features.forEach(function(marker) {
var el = document.createElement("div");
el.id = "marker-" + marker.properties.id;
el.className = "marker";
new mapboxgl.Marker(el, { offset: [0, -23] })
.setLngLat(marker.geometry.coordinates)
.addTo(this.map);
});
},
},
};
My issue here is that the TheMap.vue does get undefined | [{geojson}] as a prop however it does not load the marker on init or even after the source is changed in the parent component.
What I expect is that the map on Load uses markers prop to build a list of markers if available else show nothing[handle undefined | null`] And update that marker list if a new set of Data is injected as prop on changed location.

Nuxt - Cannot access data inside a method

I'm using CKEditor 5 + CKFinder (Modal Mode) to select an image using the #click event. The problem is that I don't have access to data inside the onInit function.
Here is the method:
data() {
return {
post: {
thumbnail: null,
},
};
},
methods: {
openModal() {
console.log(this.post.thumbnail) // NO PROBLEM! this.post.thumbnail IS ACCESSIBLE
CKFinder.modal( {
chooseFiles: true,
width: 800,
height: 600,
onInit: function( finder ) {
finder.on( 'files:choose', function( evt ) {
var file = evt.data.files.first();
this.post.thumbnail = file.getUrl(); // PROBLEM !! $this.post is undefined
} );
}
} );
},
},
And this is my Template:
<div class="btn btn-danger" #click="openModal">Choose Image</div>
<img class="mx-auto d-block" :src="post.thumbnail" />
As mentioned in the comment, you need to use an arrow function to resolve this in the vue object.
Whenever you use function () {}, this refers to the properties of the function, not the Vue object that you intend to reference
// example
methods () {
openModal() {
onInit: function () {
this.post // 'this' is the onInit function, not the Vue object
}
}
}
// solution
methods () {
openModal() {
onInit: () => {
this.post // 'this' is the Vue object
}
}
}
Answer
data() {
return {
post: {
thumbnail: null,
},
};
},
methods: {
openModal() {
console.log(this.post.thumbnail) // NO PROBLEM! this.post.thumbnail IS ACCESSIBLE
CKFinder.modal( {
chooseFiles: true,
width: 800,
height: 600,
onInit: finder => {
finder.on( 'files:choose', evt => {
var file = evt.data.files.first();
this.post.thumbnail = file.getUrl(); // PROBLEM !! $this.post is undefined
});
}
});
},
},

How to use a component multiple times while passing different data to it?

I'm trying to create a snackbar component for showing simple notifications. It can be used at many places in the entire application as well as on a single page as well. I've created a component as child component and imported it in the parent component where i want to use it. In this parent component many times this child can be used. How should i implement in a way that each time this component is called it gets its appropriate data(Ex. for error color=red text="error", for success color="green" message="success).
Any suggestions on how to implement it?
parent.vue----------------------------
<snackbar
:snackbar="snackbar"
:color="color"
:text="message"
v-on:requestClose="close"
/>
data() {
return {
snackbar: false,
color: "orange",
timeout: 3000,
message: "calling from employee compoenent"
};
},
methods: {
hello() {
console.log("button clicked!!!");
this.snackbar = true;
},
close() {
this.snackbar = false;
},
child.vue-----------------------------------------------
<template>
<v-snackbar v-model="snackbar" right top :timeout="timeout" :color="color"
>{{ text }}
<v-btn dark text #click.native="$emit('requestClose')">Close</v-btn>
</v-snackbar>
</template>
<script>
export default {
name: "snackbar",
data() {
return {
timeout: 3000
};
},
props: ["snackbar", "text", "color"],
};
</script>
<style></style>
Recommended would be to create a custom wrapper Vue plugin
plugins/snackbar/index.js
import snackbar from './snackbar.vue'
export default {
install (Vue) {
// INSTALL
if (this.installed) return
this.installed = true
// RENDER
const root = new Vue({ render: h => h(snackbar) })
root.$mount(document.body.appendChild(document.createElement('div')))
// APIs
let apis = Vue.prototype['$snackbar'] = {
show: ({ text="Foo", color="blue" }) => root.$emit('show', { text, color }), // SHOW
hide: () => root.$emit('hide') // HIDE
}
Vue.prototype['$snackbar'] = apis
Vue.snackbar = apis
}
}
plugins/snackbar/snackbar.vue
<template>
<v-snackbar right top v-model="show" :timeout="timeout" :color="color">
{{ text }}
<v-btn dark text #click.native="this.show = false">Close</v-btn>
</v-snackbar>
</template>
<script>
export default {
name: "snackbar",
data() {
return {
show,
timeout: 3000,
text: "",
color: ""
};
},
mounted () {
// LISTENING :: SHOW
this.$root.$on('show', ({ text, color }) => {
this.text = text
this.color = color
this.show = true
})
// LISTENING :: HIDE
this.$root.$on('hide', () => this.show = false)
}
};
</script>
// main.js
import Snackbar from './plugins/snackbar/index.js'
Vue.use(Snackbar)
To show / hide it in any component
this.$snackbar.show({ text: "Foo bar", color: "red" }) // OR
Vue.snackbar.show({ text: "Foo bar", color: "red" })
As per use case, you can keep updating your plugin with more params / APIs.
Alternative: By using an event bus
event-bus/bus.js
// Create an event bus
import Vue from 'vue'
export default new Vue()
app.vue
<template>
// Render the component in app.vue
<v-snackbar
right top
v-model="snackbar.show"
:timeout="snackbar.timeout"
:color="snackbar.color"
>
{{ snackbar.text }}
<v-btn
dark text
#click.native="this.snackbar.show = false"
>
Close
</v-btn>
</v-snackbar>
</template>
<script>
import bus from './event-bus/bus.js'
export default {
data () {
return {
snackbar: {
show: false,
text: '',
color: '',
timeout: 3000
}
}
},
mounted () {
// LISTEN TO SHOW
bus.$on('show', ({ text, color }) => {
this.snackbar.text = 'foo'
this.snackbar.color = 'red'
this.snackbar.show = true
})
// LISTEN TO HIDE
bus.$on('hide', () => this.snackbar.show = false)
}
}
</script>
To show / hide snackbar from any component
import bus from './event-bus/bus.js
export default {
mounted () {
bus.emit('show', { text: 'Foo bar baz', color: 'orange' }) // TO SHOW
// bus.emit('hide') // TO HIDE
}
}
Another way: By using Vuex
Render the <v-snackbar> in app.vue as done in an alternative approach & use Vuex state / getters to pass the value to the props of v-snackbar.
I did it by using combination of global components and Vuex. The answer is a bit lengthy because I provide example along the description, please bear with me :)
I first create a snackbar store with color and text as its state and a setSnackbar() action which receives color and text as params. Then you can create your Snackbar component and don't forget to have your getters, actions mapped into it. Some code snippet:
// snackbar component
<template>
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="6000" bottom right>
{{ snackbar.text }}
<v-btn dark text #click="snackbarClosed()">Close</v-btn>
</v-snackbar>
</template>
<script lang="ts">
import Vue from "vue";
import { mapGetters, mapActions } from "vuex";
export default Vue.extend({
computed: {
...mapGetters(["snackbar"])
},
methods: {
snackbarClosed() {
this.resetSnackbar();
},
...mapActions(["resetSnackbar"])
}
});
</script>
// snackbar store
const state = {
snackbar: {
show: false,
text: '',
color: ''
}
};
const getters = {
snackbar: (state: any) => state.snackbar
};
const actions = {
async setSnackbar({ commit }, params) {
commit('updateSnackbar', Object.assign({}, { show: true }, params))
},
async resetSnackbar({ commit }) {
const setting: SnackbarSetting = {
show: false,
text: '',
color: ''
};
commit('updateSnackbar', setting)
};
const mutations = {
updateSnackbar: (state: any, snackbar: SnackbarSetting) => {
state.show = snackbar.show;
state.text = snackbar.text;
state.color = snackbar.color;
}
};
To make Snackbar component globally available, import your Snackbar component into your main.ts and add the line Vue.component('Snackbar', Snackbar); before new Vue. Its purpose is to register your Snackbar component globally before initializing the Vue instance. Example:
// main.ts
import Snackbar from './components/Snackbar.vue';
Vue.component('Snackbar', Snackbar);
new Vue({
...
Before you want to display your snackbar in the app, by my recommendation you should place <Snackbar /> in your App.vue so that the snackbar can appear before your components and you won't be facing missing snackbar when changing between components.
When you want to display your snackbar, just do this in your component:
// any of your component
methods: {
someEvent() {
this.someApiCall({
// some data passing
}).then(() => {
this.setSnackbar({
text: 'Data has been updated.',
color: 'success'
});
}).catch(() => {
this.setSnackbar({
text: 'Failed to update data.',
color: 'error'
});
});
},
...mapActions(['setSnackbar'])
}
Hope you can work it out, please do not hesitate to let me know if you need something. Here's some extra material for you: Global component registration
you can watch for props in child this'll make color changes when any change happen in parent:
watch: {
color: function(value) {
"add color value to your dom css class"
}
}

test Vue js props component with Vue-test-utils

I'm new on testing Vue apps, I'm trying to test props in one Vue component, using Vue-test-utils package. I'm wondering if I'm creating the propsData is the proper way or there is another approach which in that case it's better to test this component successfully
Template.spec.js
import { mount } from '#vue/test-utils';
import Template from '~/components/Template.vue';
describe('Template', () => {
const wrapper = mount(Template, {
propsData: {
image: 'https://loremflickr.com/640/480/dog',
location: 'London',
jobPosition: 'Developer',
hashtags: ['es6', 'vue']
}
});
it('should render member props', () => {
expect(wrapper.props().image).toMatch('https://loremflickr.com/640/480/dog');
expect(wrapper.props().location).toMatch('London');
expect(wrapper.props().jobPosition).toMatch('Developer');
expect(wrapper.props().hashtags).toEqual(['es6', 'vue']);
});
});
Template.vue
<template>
<div class="template">
<img
:src="image"
>
<span>{{ location }}</span>
<span>{{ jobPosition }}</span>
</div>
</template>
<script>
export default {
name: 'template',
props: {
image: {
type: String,
required: true
},
location: {
type: String,
required: true
},
jobPosition: {
type: String,
required: true
},
}
};
</script>
You can test not props value, but component's behavior depending on props set. For example, your component can set some classes or show some element if some prop is set
e.g.
describe( 'BaseButton.vue icon rendering', () => {
const icon = 'laptop';
const wrapper = shallowMount( BaseButton, {
propsData : {
icon : icon,
},
} );
const wrapperWithoutIcon = shallowMount( BaseButton );
it( 'renders icon component', () => {
expect( wrapper.contains( FontAwesomeIcon ) ).toBe( true );
} );
it( 'sets a right classname', () => {
expect( wrapper.classes() ).toContain('--is-iconed');
} );
it( 'doesn\'t render an icon when icon prop is not passed', () => {
expect( wrapperWithoutIcon.contains( FontAwesomeIcon ) ).toBe( false );
} );
it( 'sets right prop for icon component', () => {
const iconComponent = wrapper.find( FontAwesomeIcon );
expect( iconComponent.attributes('icon') ).toMatch( icon );
} );
} );
Your Template.spec.js is fine, in which you set up your fake props.
You can check if those fake props are rendered out into the HTML.
Here is an example:
it('title render properly thru passed prop', () => {
const wrapper = shallowMount(app, {
propsData: {
data: {
title: 'here are the title'
}
},
})
expect(wrapper.text()).toContain('here are the title')
})
This way, you are checking if your code can render your props to HTML

How to destroy vuejs component from outside of component

I have created one of component in vuejs something like this
var tree_data = Vue.extend({
template: '#tree_data_template',
props: [
'groupmodal',
'search-name',
'tree-id'
], // props in single quotes
data: function () {
return {
shared: d2d.store
};
}
});
And use this component in another template like this.
var template_data = Vue.extend({
template: '#template_data',
created: function () {
var self = this;
self.shared.setCurrentRoute('templates');
},
components: {
'tree-data': tree_data
},
data: function () {
return {
id: this.$route.params.id,
shared: d2d.store,
};
},
methods: {
destroyComponent: function () {
//Need to code for destroy tree-data component
}
}
});
Blade file code
<tree-data
groupmodal="false"
search-name="user_search"
tree-id="user_tree"
>
</tree-data>
So finally how can i destroy my "tree-data" component through the "destroyComponent()" method
As cobaltway said you can use v-if
Setting v-if initially to false will render(generate) the component.
Then in your method setting v-if to true will destroy the component.
html
<div id="template_data">
<tree-data v-if="destroyComponent"></tree-data>
</div>
script
var template_data = Vue.extend({
template: '#template_data',
created: function () {
var self = this;
self.shared.setCurrentRoute('templates');
},
components: {
'tree-data': tree_data
},
data: function () {
return {
id: this.$route.params.id,
shared: d2d.store,
destroyComponent:true
};
},
methods: {
destroyComponent: function () {
//Need to code for destroy tree-data component
this.destroyComponent = false;
}
}
});
Here is the fiddle