b-field value is getting updated only #select and not #input? - vue.js

I am using the Buefy UI components in my VueJS project. I have a drop-down in a page:
<b-field label="Business Unit">
<b-autocomplete
:data="dataBusinessUnit"
placeholder="select a business unit..."
field="businessUnit"
:loading="isFetching"
:value="this.objectData.businessUnit"
#typing="getAsyncDataBusinessUnit"
#select="(option) => {updateValue(option.id,'businessUnit')}"
>
<template slot-scope="props">
<div class="container">
<p>
<b>ID:</b>
{{props.option.id}}
</p>
<p>
<b>Description:</b>
{{props.option.description}}
</p>
</div>
</template>
<template slot="empty">No results found</template>
</b-autocomplete>
</b-field>
As you can see from the above code, the updateValue function is responsible for updating the value, but it will currently be called only when the user selects something from the drop-down suggestions. I want the value to be updated even when the user starts to type something. Example: #input="(newValue)=>{updateValue(newValue,'businessUnit')}". However, there is already a debounce function called getAsyncDataBusinessUnit that I am calling to fetch the filtered autocomplete results based on what the user has typed during the #typing event.

According to the Buefy Autocomplete API documentation found here, you could probably use v-model instead of using value directly.
Alternatively you could actually implement the #input like you wrote yourself, the #typing event shouldn't interfere with it.
Or you could just handle the value updating in #typing:
#typing="onTyping"
// then later in JS...
methods: {
onTyping(value) {
this.updateValue(value, 'businessUnit')
this.getAsyncDataBusinessUnit(value)
},
}

Related

Vue changes to array not updating the DOM

I'm trying to make changes to an array in the parent component (insert and update data) that is passed to the child component via Props, but the DOM is not being updated.
Parent component:
<UsersList
v-for="(role, i) in userRolesNames"
:key="i"
:users="usersPages[i].data"
/>
Child component:
<template>
<div
v-for="user in users"
:key="user.id"
>
<span>{{ user.name }}</span>
<div>
<i
class="bi bi-pen-fill edit-icon m-pointer"
#click="onClickEdit(user)"
></i>
<i
class="bi bi-trash-fill delete-icon ms-2 m-pointer"
#click="onClickDelete(user)"
></i>
</div>
</div>
</template>
<script lang="ts">
// recieving the array
props: {
users: {
required: true,
type: Array as PropType<usersType[]>
}
}
</script>
Each user basically has the following structure:
{
id: x,
name: 'x',
email: 'x',
login: 'x',
role: x
}
The problem is that when trying to insert or update a record in the usersPages[i].data array, the DOM doesn't change. If I use the Vue developer tools, the data is changing correctly, but the DOM isn't.
Tried inserting using the push method on the array but without success. The only thing that worked was this:
const newUser = response.data;
const page = this.usersPages[newUser.role - 1];
Vue.set(page, 'data', [...page.data, newUser]);
To update I tried to directly change the user properties, but like the insert the DOM doesn't update. What worked was:
const page = this.usersPages[user.role - 1];
const oldUsers = page.data.filter(u => u.id != user.id);
Vue.set(page, 'data', [ ...oldUsers, response.data ]);
Works but doesn't look right... Can anyone help?
I had a similar problem before. I believe that this is a bug in vue. The reason for this is that Vue rendered the v-for already. Vue as of now does not know how to handle the changes in the array. What I did as a workaround for this is have a updateKey variable inside my script set to 0. Then increment this variable every time we update the array updateKey++. And we use this key for our component.
On child component,
<template>
<span
v-for="user in users"
:key="user.id"
>
<div :key="updateKey">
<span>{{ user.name }}</span>
<div>
<I
class="bi bi-pen-fill edit-icon m-pointer"
#click="onClickEdit(user)"
></i>
<I
class="bi bi-trash-fill delete-icon ms-2 m-pointer"
#click="onClickDelete(user)"
></i>
</div>
</div>
</span>
</template>
and onClickEdit(user) and onClickDelete(user) make sure you have updateKey++
The DOM won't update because you are not doing it reactively. To update this value reactively you should listen to some event (input for example), that you'll emit in the child component and pass the new(!) value to your property. But a better way is to bind your components via v-model.
P.S. - So you can see changes in the devtools because this is not a part of the Vue lifecycle, it`s just like a parser that watches actual data, but without context.
I decided to create an example to show #peperoneen how I did it, as I believed the way was correct. But in the example itself, it worked and in my project, it didn't, and the only difference was the way the list was initially filled.
To load the users I do a request to the API that returned the paged data, which I normally assigned with the '=' operator. However, I decided to do the assignment step by step and in the users part, I used the 'push' method. This way, the operations with the list using push and filter work normally, updating the DOM and without the need to use Vue.set
I think the problem was the way the list was being generated. I wasn't doing it reactively I guess...

Vue, change native event not working, input native does

I have a question and maybe a Vue bugg.
I have a custom component that needs a #change.native event. But it does not trigger anything and I could not find anything about this issue myself.
So i tried some different stuff and like #click.native and #input.native does work. Even tho #input.native works and do the trick i want to, i still want to know why the change event does not work.
Anybody? Else I should report this.
Vue version: 2.5.2
<custom-input type="search"
placeholder="search"
v-model="search"
#input.native="change" />
If the <input /> inside the custom component is a child element of another element, then the event listener bound by the .native modifier will not be reached, since it is listening to the event of a different element.
custom-input.vue:
<template>
<div>
<input :value="someValue" />
</div>
</template>
<script>
export default {
props: ['value']
}
</script>
so if you have this scenario, then the #change.native will be bound on the <div> (the wrapper).
(sadly) You need to manually propagate the event manually.

How to avoid vue component redraw?

I have prepared tag input control in Vue with tag grouping. Templates includes:
<script type="text/x-template" id="tem_vtags">
<div class="v-tags">
<ul>
<li v-for="(item, index) in model.items" :key="index" :data-group="getGroupName(item)"><div :data-value="item"><span v-if="typeof model.tagRenderer != 'function'">{{ item }}</span><span v-if="typeof model.tagRenderer == 'function'" v-html="tagRender(item)"></span></div><div data-remove="" #click="remove(index)">x</div></li>
</ul>
<textarea v-model="input" placeholder="type value and hit enter" #keydown="inputKeydown($event,input)"></textarea>
<button v-on:click="add(input)">Apply</button>
</div>
</script>
I have defined component method called .getGroupName() which relays on other function called .qualifier() that can be set over props.
My problem: once I add any tags to collection (.items) when i type anything into textarea for each keydown .getGroupName() seems to be called. It looks like entering anything to textarea results all component rerender?
Do you know how to avoid this behavior? I expect .getGroupName to be called only when new tag is added.
Heres the full code:
https://codepen.io/anon/pen/bKOJjo?editors=1011 (i have placed debugger; to catch when runtime enters .qualifier().
Any help appriciated.
It Man
TL;DR;
You can't, what you can do is optimize to reduce function calls.
the redraws are dynamic, triggered by data change. because you have functions (v-model and #keydown) you will update the data. The issue is that when you call a function: :data-group="getGroupName(item)" it will execute every time, because it makes no assumptions on what data may have changed.
One way of dealing with is is setting groupName as a computed key-val object that you can look up without calling the function. Then you can use :data-group="getGroupName[item]" without calling the function on redraw. The same should be done for v-html="tagRender(item)"
Instead of trying to fight how the framework handles data input events and rendering, instead use it to your advantage:
new Vue({
el: '#app',
template: '#example',
data() {
return {
someInput: '',
someInputStore: []
};
},
methods: {
add() {
if (this.someInputStore.indexOf(this.someInput) === -1) {
this.someInputStore.push(this.someInput);
this.someInput = '';
}
},
}
});
<html>
<body>
<div id="app"></div>
<template id="example">
<div>
<textarea v-model="someInput" #keyup.enter.exact="add" #keyup.shift.enter=""></textarea>
<button #click="add">click to add new input</button>
<div>
{{ someInputStore }}
</div>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
</body>
</html>
Event modifiers modifying
In the example, you can see that I am using 4 different event modifiers in order to achieve the desired outcome, but I will focus on the combination of them here:
#keyup.enter.exact - allows control of the exact combination of system modifiers needed to trigger an event. In this case, we are looking for the enter button.
#keyup.shift.enter - this is the interesting bit. Instead of trying to hackily prevent the framework from firing on both events, we can use this (and a blank value) to prevent the event we added into #keyup.enter.exact from firing. I must note that ="" is critical to the whole setup working properly. Without it, you aren't giving vue an alternative to firing the add method, as shown by my example.
I hope this helps!

v-on:change does not work for vue-multiselect

I am using vue-multiselect component in my vue.js project, I am using v-on directive to execute a function on the change event ,
<multiselect v-model="selected" :options="projects" :searchable="false" :custom-label="customLabel" track-by="name" v-on:change="executeLoader">
<template slot="customLabel" slot-scope="{ customLabel }"><strong>{{ option.name }}</strong></template>
</multiselect>
I have example full code here: https://codesandbox.io/s/yjjon0vzxj
the v-on:change was working with <select> component but it stopped workigng with vue-multiselect ! I tried with v-on:click="executeLoader" but that too didnt worked either..
#click will not trigger the method executeLoader with vue multiselect. You can use #input - which is similar to v-on:change, #close, #select as in example below:
<multiselect placeholder="Pick at least one"
select-label="Enter doesn’t work here!"
:value="value"
:options="options"
:multiple="true"
:searchable="true"
:allow-empty="false"
:hide-selected="true"
:max-height="150"
:max="3"
:disabled="isDisabled"
:block-keys="['Tab', 'Enter']"
#input="onChange"
#close="onTouch"
#select="onSelect">
</multiselect>
In your case I would try #input="executeLoader"
In vue-multiselect, since it is a component you can't treat it to behave like a simple <select> element.
In components, when you expect them to behave and "listen" to click events just like other html tag, then you should add an event modifier called: .native.
So, you can do on any component:
<... #click.native="executeLoader" />
But that is not what you are looking for I think. You want to trigger a function when you add more and more tags, or in short: when the selected items increase.
for that, vue-multiselect exposes the #input event, so you can handle using:
<... #input="executeLoader" />
And now just call executeLoader and accept the arguments as:
methods: {
executeLoader(selectedItems) {}
}

Vue v-model not reactive with BS4 radio button group

I'm hoping I'm just missing something simple because I've been looking at this for too long, but I'm stumped.
I have a form with inputs bound to vuejs. I have a group of 2 radio buttons for selecting the "gender", and the binding is working perfectly. If I click on either of the radio buttons, I can see the data change in the vue component inspector.
But I'm trying to change the radio buttons to a Bootstrap 4 button group, and can't seem to get the v-model binding to work. No matter what I try, the gender_id in my vue data is not getting updated when I click either of the buttons in the button group.
The form input values are being fed in through vue component properties, but for simplicity, my data for the radio buttons/button group would look like this:
export default {
data() {
return {
genders: {
1: "Men's",
2: "Women's"
},
gender_id: {
type: Number,
default: null
}
}
}
}
Here is the code I have for the radio button version (which is working properly):
<div class="form-group">
<label>Gender:</label>
<div>
<div class="form-check form-check-inline" v-for="(gender, key) in genders" :key="key">
<input type="radio"
class="form-check-input"
name="gender_id"
:id="'gender_' + key"
:value="key"
v-model.number="gender_id">
<label class="form-check-label" :for="'gender_' + key">
{{ gender }}
</label>
</div>
</div>
</div>
Here is the button group version that is not properly binding to the gender_id data in vue.
<div class="form-group">
<label>Gender:</label>
<div>
<div class="btn-group btn-group-toggle" data-toggle="buttons">
<label class="btn btn-outline-secondary" v-for="(gender, key) in genders" :key="key">
<input type="radio"
class="btn-group-toggle"
name="gender_id"
:id="'gender_' + key"
:value="key"
autocomplete="off"
v-model.number="gender_id">
{{ gender }}
</label>
</div>
</div>
</div>
I've been using the following Boostrap 4 documentation to try to get this working.
https://getbootstrap.com/docs/4.0/components/buttons/#checkbox-and-radio-buttons
In the documentation for button groups they don't even include the value property of the radio inputs, whereas they do include it in the documentation for form radio buttons.
https://getbootstrap.com/docs/4.0/components/forms/#checkboxes-and-radios
Is this for simplicity or do button groups of radio buttons not even return the value of the checked button?
I see other threads stating that buttons groups are not meant to function as radio buttons, but if that's true for BS4, then why would Bootstrap have button groups with radio buttons as they do in their documentation referenced above? If you can't retrieve the checked state, then why not just use a <button> instead of <label><input type=radio></label>?
Any ideas as to what I'm doing wrong and/or not understanding correctly?
Thanks so much!
Thanks so much to #ebbishop for his helpful insights.
The issue was related to vue and bootstrap both trying to apply javascript to the buttons in the button group.
To get around this issue, it was as simple as removing data-toggle="buttons" from the button group. By removing the data-toggle attribute, the bootstrap js is not applied and vue can manage the button group.
Nothing is actually wrong your use of v-model here.
However: you must add the class "active" to the <label> that wraps each radio-button <input>.
See this fiddle for a working example.
Is that what you're after?