Vue how to watch inner values of json data - vue.js

I have 'd' array in data and I want to watch if its elements gets changed, but some of the elements are nested.
My example is basically if I change the value Apple to Axe watcher should execute the methods inside of it, How do I achieve it?
<template>
<div>
<button #click="change">Click me</button>
{{ d }}
</div>
</template>
<script>
export default {
watch: {
d: function() {
this.hello();
},
},
data() {
return {
d: [{ a: 'apple' }, { b: 'bananana' }, 'c'],
};
},
methods: {
hello() {
alert('Executed');
},
change() {
this.d[0].a = 'axe';
},
},
};
</script>
<style></style>

Use deep: true on your watcher
watch: {
d: {
handler(val) {
this.hello();
},
deep: true
}
},

Related

How do I check if the Multi-select component in Vue js is empty?

I am trying to check if the Multi-select component is empty. But upon checking it kept telling me it's not null. Do I need to use a v-model for this?
Here's the code for the Multi-select component:
<template>
<div>
<Multiselect
v-model="value"
mode="tags"
:close-on-select="false"
:searchable="false"
:create-option="true"
:options="multi_options"
class="multiselect-orange multiselect"
/>
</div>
</template>
<script>
import Multiselect from "#vueform/multiselect";
export default {
components: {
Multiselect,
},
props: {
multi_options: {
type: Array,
required: true,
},
inputSelected:{
required:true,
},
method: { type: Function },
// default: {
// type: String,
// required: false,
// default: null,
// },
},
data() {
return {
value: this.inputSelected,
};
},
mounted() {
this.$emit("set-input", this.value);
},
watch: {
value: function () {
this.$emit("set-input", this.value);
},
},
};
</script>
Here's the code where I used the component:
<div class="inputHolder">
<label class="body"> Skills:</label>
<MultiSelect :multi_options="this.skills" #set-input="setSkillSet" :inputSelected="staffSkills"/>
</div>
Here's the code when I tried to check if the Multi-select is empty:
checkForm(){
this.errors = []
if (this.staffSkills) {
return true;
}
if(this.staffSkills === []){
this.errors.push('Select at least one.');
}
if(this.errors.length){
return this.errors
}
}
},

how to create vue-shepperd component

I am trying to develop guided tour with shepherd: https://www.npmjs.com/package/vue-shepherd but I cannot get the element. So here is my component for guide tour:
<template>
<div></div>
</template>
<script>
import { useShepherd } from 'vue-shepherd';
export default {
props: {
element: {
required: true,
},
id: {
type: Number,
required: true,
},
title: {
type: String,
},
text: {
type: String,
required: true,
},
position: {
type: String,
required: true,
},
},
mounted() {
this.tour.start();
},
data() {
return {
tour: null,
};
},
methods: {
createTour() {
this.tour = useShepherd({
useModalOverlay: true,
});
this.tour.addStep({
title: this.title,
text: this.text,
attachTo: { element: this.element, on: this.position },
buttons: [
{
action() {
return this.back();
},
classes: 'shepherd-button-secondary',
text: 'Back',
},
{
action() {
return this.next();
},
text: 'Next',
},
],
id: this.id,
});
this.tour.start();
},
},
created() {
this.createTour();
},
};
</script>
and here is my parent component:
<button ref="button">
Click
</button>
<guide :element="element" :title="'Tour'" :text="'Example'" :position="'bottom'" :id="1" />
and the mounted of the parent element:
mounted() {
this.element = this.$refs.button;
},
but the tour doesnt attach the the button element. it just appears in the middle of the page. Why do you think it is?
Looks like a usage problem in vue hooks. The child component's hooks fire before the parent component's hooks. Therefore, at the time of the creation of the tour, the element does not exist. Vue-shepherd does not use vue reactivity.
Use
mounted() {
this.$nextTick(() => {
this.createTour();
});
},
Codesanbox
But it's better to change the component structure. If you are using vue3 you can use my package
In this case it will look like this
<template>
<button v-tour-step:1="step1">
Click
</button>
</template>
<script>
import { defineComponent, inject, onMounted } from "vue";
export default defineComponent({
setup() {
const tour = inject("myTour");
onMounted(() => {
tour.start();
});
const step1 = {
/* your step options */
}
return {
step1,
};
}
});
</script>

v-if is not updated when importing a 3rd party script with vue-meta

I want to use vue-meta to import a 3rd party SwiperJS script into my nuxt website.
The script is loaded only after mounted()-hook is called so I also initialize it on update(). This kind of works but the v-if on the wrapper does not update.
<template>
<section
class="swiper"
v-if="isSwiperLoaded"
>
<div class="swiper-wrapper"> ... </div>
</section>
</template>
<script>
export default {
data() {
return { swiperLoaded: false, swiperInitialized: false }
},
computed: {
isSwiperLoaded: {
get() {
return this.swiperLoaded
},
set(value) {
this.swiperLoaded = value
},
},
isSwiperInitialized: {
get() {
return this.swiperInitialized
},
set(value) {
this.swiperInitialized = value
},
},
},
head() {
return {
script: [
{
hid: 'swiper',
src: 'https://cdn.jsdelivr.net/npm/swiper#8/swiper-bundle.min.js',
defer: true,
// Changed after script load
callback: () => {
this.isSwiperLoaded = true
},
},
],
link: [
{
rel: 'stylesheet',
type: 'text/css',
href: 'https://cdn.jsdelivr.net/npm/swiper#8/swiper-bundle.min.css',
},
],
}
},
methods: {
initSwiper() {
const swiperOptions = {
...
}
let swiper = new Swiper(this.$el, swiperOptions)
},
},
mounted() {
if (!this.isSwiperInitialized && this.isSwiperLoaded) {
console.log('INIT LOADED')
this.initSwiper()
this.isSwiperInitialized = true
}
},
updated() {
if (!this.isSwiperInitialized && this.isSwiperLoaded) {
console.log('UPD LOADED')
this.initSwiper()
this.isSwiperInitialized = true
}
},
}
</script>
Also I noticed the computed values are normally for getting and setting values from store and not local values. Maybe there is an easier way of updating the variables.

Rendering multiple components dynamically from an array

I am trying to render components dynamically within my child component named ChildTabs. I would like to render the components based on the array that passed into the component from the parent view named Contatcs.
So for example if I pass the the form_type from the contact_type named "Big" it would render on my tabs vue. However I have other data that contains more than one component I am trying to render, such as medium which contains multiple forms such as Red Green & Blue.
I have an idea of creating a for loop in my method using the prop data form_type, so I can retrieve my list of forms I am trying to call, but that is where I do not know what to do next. I am already importing the forms, I am not sure how to render them.
Any suggestions are welcomed.
Contacts.vue
<template>
<div class="row">
<ChildTabs
:form_type="contact_types"
/>
</div>
</template>
<script>
'use strict';
import ChildTabs from '../tabs';
export default {
name: 'contacts-view',
data: function () {
return {
form_data: {},
failed_validations: [],
contact_type: '',
contact_types: [
{
label: 'Big',
value: 'big',
form_type: [
'Red'
]
},
{
label: 'Medium',
value: 'medium',
form_type: [
'Red',
'Green',
'Blue'
]
},
{
label: 'Small',
value: 'small',
form_type: [
'Blue',
'Green'
]
},
],
}
},
props: {
},
computed: {
},
methods: {
},
components: {
ChildTabs
}
}
</script>
Tabs.vue
<template>
<!--=============================================================-->
<!-- Contact Forms -->
<!--=============================================================-->
</template>
<script>
'use strict';
import Red from './forms/red';
import Green from './forms/green';
import Blue from './forms/blue';
export default {
name: 'forms-tab',
data: function () {
return {
}
},
props: {
form_type: {
type: Array,
required: false,
default: []
},
},
computed: {
},
methods: {
RenderComponent: function ()
{
this.$props.form_type
}
},
created: function () {
this.RenderComponent();
},
components: {
Red,
Blue,
Green
}
}
</script>
You can use dynamic component in Vue
<template>
<div>
<div v-for="type in form_type" :key="type">
<component :is="getCompentName(type)"/>
</div>
</div>
</template>
<script>
export default {
...
methods: {
getCompentName(type) {
switch (type) {
case 'red':
return 'red'
case 'blue':
return 'blue'
case 'green':
return 'green'
default:
return 'red'
}
}
}
}
</script>

VueJs: Form handling with Vuex and inputs generated with an API

Here's an example of a component:
<script>
export default {
name: 'my-form',
computed: {
myModules() {
return this.$store.state.myModules;
}
}
</script>
<template>
<form>
<p v-for="module in myModules">
<input type="checkbox" :value="module.id" />
<label>module.name</label>
</p>
<button type="submit">Submit</button>
</form>
</template>
The associated store:
state: {
myModules: []
},
mutations: {
setModules(state, modules) {
state.myModules = modules;
}
},
actions: {
getModules({commit}) {
return axios.get('modules')
.then((response) => {
commit('setModules', response.data.modules);
});
}
}
And finally, an example of return of the API "getModules":
modules : [
{
id: 1,
name: 'Module 1',
isActive: false
},
{
id: 2,
name: 'Module 2',
isActive: false
},
{
id: 3,
name: 'Module 3',
isActive: false
}
]
My question: what's the best way to change the "isActive" property of each module to "true" when I check the checkbox corresponding to the associated module, directly in the store?
I know that Vuex's documentation recommends to use "Two-way Computed Property" to manage the forms, but here I don't know the number of modules that the API can potentially return, and I don't know their name.
Thank you in advance!
This is a little bit wicked approach, but it works. You can create an accessor object for every item you access in a loop:
const store = new Vuex.Store({
mutations: {
setActive (state, {index, value}) {
state.modules[index].isActive = value
}
},
state: {
modules : [
{
id: 1,
name: 'Module 1',
isActive: false
},
{
id: 2,
name: 'Module 2',
isActive: false
},
{
id: 3,
name: 'Module 3',
isActive: false
}
]
}
});
const app = new Vue({
el: '#target',
store,
methods: {
model (id) {
const store = this.$store;
// here i return an object with value property that is bound to
// specific module and - thanks to Vue - retains reactivity
return Object.defineProperty({}, 'value', {
get () {
return store.state.modules[id].isActive
},
set (value) {
store.commit('setActive', {index: id, value});
}
});
}
}
})
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex/dist/vuex.min.js"></script>
<div id="target">
<div v-for="(item, id) in $store.state.modules">
Module #{{ item.id }} state: {{ item.isActive }}
</div>
<div v-for="(item, id) in $store.state.modules">
<label>
Module #{{ item.id }}
<input type="checkbox" v-model="model(id).value"/>
</label>
</div>
</div>
This is still quite a messy approach, but at least you don't have to commit mutations directly in template. With a little help of Vue.set() you can use this approach even to overcome standard reactivity caveats.
I have an alternative solution for you. You could make a child component for the checkboxes to clean up the code a bit.
UPD: I just realised that everything that I and #etki proposed is an overkill. I left the old version of my code below in case you still want to take a look. Here is a new one:
const modules = [{
id: 1,
name: 'Module 1',
isActive: true,
},
{
id: 2,
name: 'Module 2',
isActive: false,
},
{
id: 3,
name: 'Module 3',
isActive: false,
},
];
const store = new Vuex.Store({
state: {
myModules: [],
},
mutations: {
SET_MODULES(state, modules) {
state.myModules = modules;
},
TOGGLE_MODULE(state, id) {
state.myModules.some((el) => {
if (el.id === id) {
el.isActive = !el.isActive;
return true;
}
})
}
},
actions: {
getModules({
commit
}) {
return new Promise((fulfill) => {
setTimeout(() => {
commit('SET_MODULES', modules);
fulfill(modules);
}, 500)
});
}
}
});
const app = new Vue({
el: "#app",
store,
data: {},
methods: {
toggle(id) {
console.log(id);
this.$store.commit('TOGGLE_MODULE', id);
}
},
computed: {
myModules() {
return this.$store.state.myModules;
},
output() {
return JSON.stringify(this.myModules, null, 2);
},
},
mounted() {
this.$store.dispatch('getModules').then(() => console.log(this.myModules));
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<script src="https://unpkg.com/vue"></script>
<div id="app">
<form>
<div v-for="data in myModules">
<label :for="data.id">{{ data.name }}: {{data.isActive}}</label>
<input type="checkbox" :id="data.id" :name="'checkbox-' + data.id" :checked="data.isActive" #change="toggle(data.id)">
</div>
</form>
<h3>Vuex state:</h3>
<pre v-text="output"></pre>
</div>
As you can see above you could just call a function on input change and pass an id as a parameter to a method that fires vuex action.
The old version of my code.
A new one on jsfiddle