Vuejs - Watcher not watching deep enough - vue.js

For some reason, Vuejs doesn't deeply watch for changes. Let's say below is the data that I'm watching.
input_collection: [
{
type: 'foo',
value: 'foo'
},{
type: 'bar',
value: 'bar'
},{
type: 'baz',
value: 'baz'
}
]
Basically its an array of objects. When I change anything except the last item, it triggers the watch to do its job. But when the last item is updated, it doesn't.
The way that I change the contents of the input_collection is by using v-model and directly changing the value in the code. I'm also suspecting the direct change method as a culprit since i only used this in the last item and the input has a read-only attribute, but since I'm directly assigning a new value for the item's content, that shouldn't be a problem.
//via v-model
<input :type="type" v-model="title"/>
//via directly changing
<input type="type" :value="value" readonly>
//js
this.input.title = new_value;
this.value = new_value;
I tried manually changing the last contents in the last item's content via the Vue Devtools Extension but it still doesn't work.

I have figured it out. Turns out that it being readonly is causing my problems.
I just created a work around to it by doing:
//markup
<input type="text" v-model="input.value" #keypress.prevent>
//js
this.input.value = new_value
Now, the watcher works as it should be.

Related

Multiple VueSelect

I am obtaining from an API the data to fill a v-select type component, the component is filled correctly and I also obtain the options that the user already had assigned (multiple)
And the v-select component are filled correctly
But when i try to add another option occurs the next
Here is the Source Code
<validation-provider
#default="validationContext"
name="skill"
>
<b-form-group
label="Skills"
label-for="skill_id"
:state="getValidationState(validationContext)"
>
<v-select
v-model="itemData.skills"
:dir="$store.state.appConfig.isRTL ? 'rtl' : 'ltr'"
multiple
:options="skillOptions"
:clearable="false"
:reduce="(val) => val.value"
input-id="skill_id"
/>
<b-form-invalid-feedback
:state="getValidationState(validationContext)"
>
{{ validationContext.errors[0] }}
</b-form-invalid-feedback>
</b-form-group>
</validation-provider>
Here i have the array of objects, with multiple elements
itemEdit.value.skills
Then map to get a reduced array with only the value and label
I have probed using only the label without value and the result is the same
const openEdit = item => {
isSidebarActive.value = true
itemEdit.value = item
itemEdit.value.skills = item.skills.map(skill => ({
value: skill.id,
label: skill.skill,
}))
isAdd.value = false
}
Everything apparently goes right, the v-model match correctly with the available options, the problem comes when the user interact with the vSelect to add another item, the already selected items disappear
Thanks in advance
Based on this page o the Vue Select Documentation, the reduce prop is usually meant for use with a label prop, and is only needed if you provide an object to v-select.
I suspect you are providing an array of primitives in your v-model (ie itemData.skills is an array of strings) instead of an array of objects. If it is an array of objects instead, then I suspect you either don't have a [label] key on the object (the default that label is set to), or you don't have a [value] key (what you are trying to return with your reduce prop).
Ideally, you would give us an example of your data coming in for us to be able to help you better.
thanks for listen, the problem was solved removing:
:reduce="(val) => val.value"
From
<v-select
v-model="itemData.skills"
:dir="$store.state.appConfig.isRTL ? 'rtl' : 'ltr'"
multiple
:options="skillOptions"
:clearable="false"
:reduce="(val) => val.value"
input-id="skill_id"
/>

bootstrap-vue table, Formatter callback error: You may have an infinite update loop in a component render function

I'm using bootstrap-vue table, according to documentation regarding formatter callback: https://bootstrap-vue.org/docs/components/table .
A variable is defined in data(), I will need this variable as a flag to control the cell content.
data() {
return {
aFlag: 0,
}
}
Then in the fields I use the formatter call back:
{ key: 'value', label: 'Value', formatter: this.updateValue},
In the methods area I use updateValue to update the flag:
methods: {
updateValue(value) {
..
aFlag = value
..
}
}
Then error "You may have an infinite update loop in a component render function." happened here.
If I want to do such a thing, is there any best practice? The cell content may cause other cell's change, so currently I use a variable to control the behavior as a flag. Thanks in adavance.
The formatter callback is only intended to change how the value is displayed to the user, not change its value.
If you want to be able to change the value, I'd suggest using a slot for the value property and use v-model on a form component within the slot:
<template v-slot:cell(value)="data">
<input type="text" v-model="data.value"/>
</template>

Get model component value from VueJS JSON Schema

I'm have a project in vuejs, Vuetify and the following library to generate forms dynamically using annotated JSON Schemas:
https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/about
The library is great and do exactly what I need, generate form from JSON scheme, I can even add my own component, and show it on a form, the only missing thing is I cant get the slot component data into the model.
you can find my example here:
https://codepen.io/eran-levi/pen/jOqjroa
This is how I added the component:
<template slot="rating" slot-scope="{key, value, update, schema }" class="field">
<star-rating :value="value" #rating-selected="update($event, key)" :show-rating="false" />
</template>
and in the Schema JSON:
rating: {
type: "number",
title: "Rating",
minimum: 0,
maximum: 5
}
as you can see, once you hit the "Show Data" button, you can see the results of each field you edit, beside the value of the new star component I have added..
what am I missing here?
Thank you.
You are missing your update method.
I added the updateRating method and bound it to the #rating-selected event.
Here is a working fiddle: https://jsfiddle.net/9pjdxht6/1/
vue-star-rating component:
<star-rating :value="value" #rating-selected="updateRating" :show-rating="false" />
updateRating method:
updateRating: function(rating){
this.model.rating = rating;
}
Hope it help you.

In vue.js is it possible to notify "observers" to refetch the value from the observed data, without changing the value of the observed data

Suppose that I have an input element bound like this:
<input :value="somedata">
The user types something in the input, and since I am not using v-model or altering somedata through a handler, the value of the element is now different from somedata. This is what I want, but I would also like to have the following capability:
Without changing the value of somedata I would like to be able to notify the element so that it sets its value equal to somedata again. Something like knockout's notifySubscribers() or valueHasMutated()
Is that possible in vue.js?
UPDATE: A clear illustration of the issue here: https://jsfiddle.net/gtezer5c/3/
It's a little difficult interpreting what exactly the requirements and acceptance criteria might be to suit your needs, and I thought Bill's solution was what you were after, but after all the updates and clarifications, I think I understand a little more what you're trying to accomplish: in short, I think you need a generic way to have an input that can hold a value but that can be independently reverted to some other value.
Please have a look at this CodePen. I believe it's providing what you're trying to do. It allows you to create an input element as a revertable component, which can optionally be passed a default value. Any changes to that input are maintained by the component with its value data property. It will not be observing/pulling in any lastKnownGood type of value because any reversion will be pushed into the component from outside.
Externally to the revertable component, you can $emit a revert event with a new value that will cause either all revertable components or a single revertable component (with a matching ID) to be reverted.
I feel like it's mostly a clean solution (assuming I'm understanding the requirements correctly), except that in VueJS 2 we have to use a standalone, shared Vue object to pass the events when there is no parent-child relationship. So you'll see:
const revertBus = new Vue()
defined in global scope in the demo. And the revertable component will use it to receive incoming messages like so:
revertBus.$on('revert', (value, id) => { ... }
and the controlling Vue object that is triggering the messages will use it like this:
revertBus.$emit('revert', this.reversionValue, targetId)
You can also emit the event with a null value to cause the revertable component to reset its value to its initial default value:
revertBus.$emit('revert', null, targetId)
Again, it's a mostly clean solution, and though it might not fit perfectly inline with what you're trying to accomplish, I'm hoping it might at least help you get closer.
I'm not sure I'm following properly but I'll give it a shot.
What I think you want is to only update some values when their "temporary" values meet some type of condition. Here's how I was thinking of it.
<div id="app">
<input v-model="tempValues.one">
<input v-model="tempValues.two">
<input v-model="tempValues.three">
<pre>{{ values }}</pre>
<pre>{{ tempValues }}</pre>
</div>
Then, in my component, I watch tempValues and only update values when a condition is met.
new Vue({
el: '#app',
data: {
values: {
one: '',
two: '',
three: '',
},
tempValues: {},
},
created () {
// Create the tempValues based on the real values...
this.tempValues = Object.assign({}, this.values)
},
methods: {
updateValues (tempValues) {
// Only updating the values if all the tempValues are longer than 3 characters...
var noneEmpty = Object.values(tempValues).every((value) => value.length > 3)
if (noneEmpty) {
this.values = Object.assign({}, tempValues)
}
},
},
watch: {
// Watch tempValues deeply...
tempValues: {
handler (tempValues) {
this.updateValues(tempValues)
},
deep: true,
},
},
})
Here's a quick demo: https://jsfiddle.net/crswll/ja50tenf/
yourvar.__ob__.dep.notify()
works on objects and arrays
Yes, You should be able to do this with help of v-on:input. You can call a function on input and put your logic of checking and updating in this function:
<input :value="somedata" v-on:input="yourMethod">
In fact if you look at the documentation, <input v-model="something"> is syntactic sugar on:
<input v-bind:value="something" v-on:input="something = $event.target.value">
so instead of assigning variable something to value inputted, you can put your logic in that place.

Vue Multi-select not showing all the options

I'm using https://vue-multiselect.js.org in my Vue component. I am using AJAX to update the select list option, and I see in the inspector that there are 10 values.
But when I actually click into the multiselect list, I only see a subset of these items and I can't figure out why.... Here are my multi-select props/events:
<multiselect
v-model="organization"
:allow-empty="true"
:loading="loading"
:options="organizationSearch"
:preserve-search="true"
#search-change="searchOrganizations"
#select="organizationSelected"
#remove="organizationDeselected"
placeholder="Search Organizations"
label="name"
track-by="id"
class="multiselect my-3"
>
</multiselect>
I figured it out. I dug into the component code a bit and found that all the options WERE in the options prop, but there were only 3 in the filteredOptions computed field. I added
:internal-search="false" into my component and then this fixed it.
I think the ultimate issue here had to do with conflict between the internal filtering, and the dynamic update of the options I was doing via AJAX.
Below is the definition of filteredOptions in the actual component definition.
It could be more helpful if you expand the options array in the inspector, so we could see what properties are presented in the objects.
track-by is used to identify the option within the options list thus it’s value has to be unique.
You set track-by="id" and label="name", so your options array should look something like this:
options: [
{ id: 1, name: 'Option #1' },
{ id: 2, name: 'Option #2' },
{ id: 3, name: 'Option #3' },
],
Make sure that all the items in your options array have an id property and it is unique for all of them, because the items with the same id will not appear in the multiselect list.
This depends on the structure of your options.
It shows that they are objects when they should probably be literal values.