Use selectize with data-binding in vuejs component with vuex - vue.js

I made a component which is basically bootstrap modal. In the modal I want to use selectize (I also tried bootstrap select). The problem is that if the select has some static values it works, while if I fill it with dynamic data it fails with: Uncaught TypeError: Cannot read property 'appendChild' of null
Here is the html:
<div class="form-group">
<label for="newContactModalType">Type*</label>
<select id="newContactModalType" name="type_id">
<option v-for="type in clientTypes" value="{{ type.id }}">{{ type.name }}</option>
</select>
</div>
And the component:
export default {
vuex: {
getters: {
clientTypes: state => state.clientTypes.list
}
},
events: {
'showNewContactModal': () => {
$('#newContactModal').modal()
}
},
ready() {
$('#newContactModalType').selectize()
}
}
I'm using vuex to get the clientTypes list. As I realize this is where something's wrong, because it works if I define clientTypes in component's data property. I'm not sure where I should call .selectize() too. The best I could think of is the ready() method.
Thanks in advance!

Related

Vue 3 two way binding with select box

I'm trying to create a two way binding between my parent (create user form) and a child component (reusable selectbox).
The parent component
<template>
<Selectbox :selectedOption="selectedRole" :options="roles" />
<span>SelectedRole: {{ selectedRole }}</span>
</template>
<script>
import Selectbox from '#/components/formElements/Selectbox.vue';
export default {
components: {
Selectbox,
},
async created() {
await this.$store.dispatch('roles/fetchRoles');
this.selectedRole = this.roles[0].value;
},
data() {
return {
selectedRole: null,
};
},
computed: {
roles() {
return this.$store.getters['roles/roles'].map((role) => ({
value: role.id.toString(),
label: role.name,
}));
},
},
};
</script>
I'm passing down the roles as options and the selectedRole variable as selectedOption.
The child component
<template>
<select :value="selectedOption" #input="(event) => $emit('update:selectedOption', event.target.value)">
<option v-for="option in options" :value="option.value" :key="option.value">{{ option.label }}</option>
</select>
</template>
<script>
export default {
props: {
options: {
type: Array,
required: true,
},
selectedOption: {
type: String,
required: false,
},
},
};
</script>
The selectedOption is assigned to the value together. When another value is selected I want to update the passed down value in the parent component. Therefore I'm using an $emit function but that's not working right now.
I also tried to use v-model to combine the value and change attributes but without success.
<select v-model="selectedOption">
What's the correct way?
Code: Codesandbox
I guess this is the handling you want to achieve: https://codesandbox.io/s/practical-orla-i8n3t?file=/src/components/Selectbox.vue
If you use v-model on a sub-component, you have to handle it properly in the sub-component.
<custom-select v-model="value" />
<!-- IS THE SAME AS -->
<custom-select
:modelValue="value"
#update:modelValue="value = $event"
/>
So if you use v-model, a property with the name modelValue gets passed down to the sub-component. If the modelValue changes (which means another option in the select list gets selected) you have to emit a change event, indicating that the modelValue got changed: $emit('update:modelValue'). v-model automatically updates it's value if this event occurs.
Source: https://learnvue.co/2021/01/everything-you-need-to-know-about-vue-v-model/

Vue.js 2: How to bind to a component method?

I have a VueJS (v2) component with a private array of objects this.private.messagesReceived which I want displayed in a textarea. The array should be converted to a string by a method/function and Vue is blocking all my attempts to bind. Every attempt results in my serialization function (converting the array to a string) only being called once and never again when the data changes.
I feel there must be a way to do this without Vue.set() or some forceUpdate shenanigans.
https://jsfiddle.net/hdme34ca/
Attempt 1: Computed Methods
Here we have the problem that Vue only calls my computed method messagesReceived1 once and never again.
<script>
{
computed: {
messagesReceived1() {
console.log("This is called once and never again even when new messages arrive");
return this.private.messagesReceived.join("\n");
},
...
methods: {
addMessage(m) {
console.log("This is called multiple times, adding messages successfully");
this.private.messagesReceived.push(m);
}
}
<script>
<template>
<textarea rows="10" cols="40" v-model="messagesReceived1"></textarea>
</template
Attempt 2: Binding Methods
Here Vue decides it doesn't like moustaches inside a textarea {{ messagesReceived2() }} and balks. It also doesn't allow messagesReceived2() or messagesReceived2 in v-model.
<script>
{
methods: {
messagesReceived2() {
return this.private.messagesReceived.join("\n");
},
addMessage(m) {
console.log("This is called multiple times, adding messages successfully");
this.private.messagesReceived.push(m);
}
}
</script>
<template>
<textarea rows="10" cols="40">{{ messagesReceived2() }}</textarea><!--Nope-->
<textarea rows="10" cols="40" v-model="messagesReceived2()"></textarea><!--Nope-->
<textarea rows="10" cols="40" v-model="messagesReceived2"></textarea><!--Nope-->
</template
You can define a data variable and set its value in the function. Then bind variable with textarea, not directly with the function.

Vue.js this.$refs empty due to v-if

I have a simple Vue component that displays an address, but converts into a form to edit the address if the user clicks a button. The address field is an autocomplete using Google Maps API. Because the field is hidden (actually nonexistent) half the time, I have to re-instantiate the autocomplete each time the field is shown.
<template>
<div>
<div v-if="editing">
<div><input ref="autocomplete" v-model="address"></div>
<button #click="save">Save</button>
</div>
<div v-else>
<p>{{ address }}</p>
<button #click="edit">Edit</button>
</div>
</div>
</template>
<script>
export default {
data() {
editing: false,
address: ""
},
methods: {
edit() {
this.editing = true;
this.initAutocomplete();
},
save() {
this.editing = false;
}
initAutocomplete() {
this.autocomplete = new google.maps.places.Autocomplete(this.$refs.autocomplete, {});
}
},
mounted() {
this.initAutocomplete();
}
}
I was getting errors that the autocomplete reference was not a valid HTMLInputElement, and when I did console.log(this.$refs) it only produced {} even though the input field was clearly present on screen. I then realized it was trying to reference a nonexistent field, so I then tried to confine the autocomplete init to only when the input field should be visible via v-if. Even with this, initAutocomplete() is still giving errors trying to reference a nonexistent field.
How can I ensure that the reference exists first?
Maybe a solution would be to use $nextTick which will wait for your DOM to rerender.
So your code would look like :
edit() {
this.editing = true;
this.$nextTick(() => { this.initAutocomplete(); });
},
Moreover if you try to use your this.initAutocomplete(); during mounting it cannot work since the $refs.autocomplete is not existing yet but I'm not sure you need it since your v-model is already empty.
I think it's because your "refs" is plural
<input refs="autocomplete" v-model="address">
It should be:
<input ref="autocomplete" v-model="address">

Interpolation doesn't rerender in parent component after custom event from child

I have select displayed in v-for loop:
<div v-for="(n, key) in selectedLanguages">
<select class="input input__col"
v-model="currentLang[key]"
#change="changeLanguage(currentLang[key], key)"
id="lang_select">
<option value="pl">Polski</option>
<option value="en">Angielski</option>
<option value="es">HiszpaƄski</option>
</select>
</div>
To each select I'm adding changeLanguage method which is:
<script>
export default {
data() {
return {
currentLang: []
}
},
methods: {
changeLanguage(value, key) {
let data = { value, key };
this.$nuxt.$emit('change::language', data);
}
},
props: ['selectedLanguages']
}
</script>
and it is in child component. In parent I'm listening for this change::language event:
this.$nuxt.$on('change::language', res => {
console.log(res);
this.selectedLanguages[res.key] = res.value;
console.log(this.selectedLanguages);
Although it's working correctly and it's updating selectedLanguages array just fine it doesn't rerender interpolation {{ selectedLanguages }} in parent. However it's correctly rerendering interpolation {{ selectedLanguages }} in child where it's passed by props. Why?
It seems like vue doesn't "catch" that selectedLanguages array have been changed. It only see when I .push or .pop this array. Is there something like apply method in vue?
I found this link in documentation: https://v2.vuejs.org/v2/guide/list.html#Caveats and added this.$set(this.selectedLanguages, res.value, res.key); in parent below my assignment but it didn't fix.
I found solution in Vue docs:
Due to limitations in JavaScript, Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
I was doing exacly like above.
The solution is instead
this.selectedLanguages[res.key] = res.value;
use
this.$set(this.selectedLanguages, res.key, res.value);
which is basically a bit weird but it works.

Vuejs v-model binding with a select box inside a slot

Good afternoon,
I seem to have occured an issue with v-model binding in scoped slots.
I've tried to create a universal API form that would allow me to hook any URL to it, add any amount and any type of DOM elements inside a scoped slot and use data fetched from API accordingly.
So far I've managed to do the first part - fetch data and pass it to elements inside; the issue I'm having now is thanks to one-way data flow - selecting an option inside of <select></select> doesn't seem to update selectId accordingly - and I get it, that's the pattern it follows... now, how do I work around it?
edit:
If I copy this select directly into the component (without passing via scoped slot) and replce props.* with just * (props.selectedId to selectedId) - it'll work flawlessly.
It only doesn't work because props are one-way.
<template>
<form :action="action" :method="method">
<slot :selectedId="selectedId" :results="results"></slot>
</form>
</template>
<script>
export default
{
props: ['action', 'method', 'url'],
data: () =>
({
results: [],
selectedId: 0,
}),
created()
{
setTimeout(() =>
{
axios.get(this.url).then(response => (this.results = response.data))
}, 500)
},
}
</script>
and HTML:
<api-form action="/blog" method="POST" url="/api/v1/blog">
<template slot-scope="props">
<select class="form-control mb-3" v-model="props.selectedId">
<option v-for="entry, i in props.results" :value="entry">#{{ entry.title }}</option>
</select>
<button class="btn btn-danger">Delete</button>
</template>
</api-form>
Gosh, I keep posting here and finding an answer afterwards.
4 hours of googling - nothing, then I post here and suddenly come up with a solution.
For anyone having the same issue, this is caused by the fact that all non-components, if you apply $emit to it, it'll be called from <Root>, meaning you have to edit your:
created()
{
this.$on('update:selectedId', (value) =>
{
this.selectedId = value
})
},
And change it to this:
created()
{
this.$root.$on('update:selectedId', (value) =>
{
this.selectedId = value
})
},
You can pass a callback as a slot prop that will modify the data being passed also as a slot prop, here's an answer of mine on a similar question v-model and scoped slots not working?