VueJS 2 component prop inconsistency when using expressions - vuejs2

I am struggling a bit passing down a piece of information into one of my components.
I am trying to iterate over a list of data and pass in a boolean set to true for the last item of the list. I am running into an interesting inconsistency where if I use v-bind:isLast="index + 1 == tiers.length" in the following code it evaluates to false but if I do v-bind:test="{index: index, tiersLength: tiers.length, bool: index + 1 == tiers.length}" and use an object to store that information rather than expecting a boolean, the bool key has a value of true within the object.
Does anyone know why this is? And also, would this be better as a computed property? I don’t really want to pass down more information from the tiers list into the component as I feel like that component should be oblivious to the tiers list altogether.
<section id="classification-metadata-editor">
<metadata-button-row
v-for="(row,index) in tiers"
v-bind:row="row"
v-bind:index="index"
v-bind:isLast="index + 1 == tiers.length"
v-bind:test="{index: index, tiersLength: tiers.length, bool: index + 1 == tiers.length}">
</metadata-button-row>
</section>
isLast: false
test: {index: 0, tiersLength: 1, bool: true}
I'm not quite sure if this is intended as I feel I am doing a bit more than what might be intended with setting the prop but I'm not quite sure how else to do it. I've been looking for similar questions but not quite sure of proper terminology for searches so my apologies if this is covered elsewhere.
Thank you!

You can not assign a object to prop like this:
v-bind:test="{index: index, tiersLength: tiers.length, bool: index + 1 == tiers.length}">
You have to create a computed property or method to get this object like following:
methods: {
getObject (index) {
return {index: index, tiersLength: this.tiers.length, bool: index + 1 == this.tiers.length}
}
}
and use this method in template:
v-bind:test="getObject(index)"

Related

Avoid converting null to empty string on b-input blur

I'm facing an issue with reactivity in Vue JS 2.6.6. Not sure if it's an issue at all or I'm missing something here.
I'm hydrating an object from MySQL DB through axios getting:
resource:Object
id: 123
alt: null
file:"2a72d890d0b96ef9b758784def23faa1.jpg"
type:"jpg"
}
The form is handling the properties via v-model:
<b-input v-model="resource.alt" />
What I can see is that on blur event, besides no changes were made, the resource.alt property is being changed from null to an empty string ""
resource:Object
id: 123
alt: ""
file:"2a72d890d0b96ef9b758784def23faa1.jpg"
type:"jpg"
}
So what could be the best way to handle this? I don't think that fetching all null fields from DB as empty strings is a nice solution. I mean something like IFNULL(files.alt,'') AS alt
On the other hand, since I fetch a lot of records by doing a loop through them all and setting all null properties to empty string looks very inefficient.
What I need is to keep null properties as null or parse in some way all null properties to empty string from the beginning.
FYI, I'm using vee-validate, so I need to validate if the object has changed with pristine flag which is set to false if the object has been manipulated. This behavior is setting the flag to false because of the change from null to ""
EDIT
This is getting odd. The way I propagate the resource to local data in the component is by clicking an element from a list.
data: () => ({
files: [],
resource: {}
})
<div v-for="file in files" :key="file.id"
#click.exact="handleIndividualCheck(file)">
{{ file.file }}
</div>
methods: {
handleIndividualCheck (item) {
this.resource = { ...item }
}
}
The first time I select an item, the null property is converted to an empty string as explained.
The next times, everything works as expected
This is in fact a bootstrap-vue bug for version 2.19.0
I've reported the issue and fixes will be merged in the next release.
Link to issue: https://github.com/bootstrap-vue/bootstrap-vue/issues/6078

Input changins in V-for does not make update placeholder and input

I'm creating a passengers form, In this form it gets adult and child counts and children ages. At the beginning childCount equals 0 and inputs of childAges are invisible. When i increase child count, they are visible one by one. So far it is ok. However while increasing childAge, input and placeholder value do not change. By the way at the the background value is changing.
I want to be updated value in input while changing. I am sharing code via jsfiddle
Please, not only fix my code, please share how it works.
Thank you.
enter code here
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. When you modify the length of the
array, e.g. vm.items.length = newLength.
Reference.
increaseChildAge: function(index) {
this.$set(this.childAges, index, this.childAges[index] + 1);
},
decreaseChildAge: function(index) {
if (this.childAges[index] > 0) {
this.$set(this.childAges, index, this.childAges[index] - 1);
}
}

Is there a way to bind a variable number of queries?

I'm coding an app for managing shift work. The idea is pretty simple: the team is shared between groups. In those groups are specific shifts. I want to get something like that:
Group 1
- shift11
- shift12
- shift13
Group 2
- shift21
- shift22
- shift23
I already made a couple of tests, but nothing is really working as I would like it to: everything reactive, and dynamic.
I'm using vue.js, firestore (and vuefire between them).
I created a collection "shiftGroup" with documents (with auto IDs) having fields "name" and "order" (to rearrange the display order) and another collection "shift" with documents (still auto IDs) having fields "name", "order" (again to rearrange the display order, inside the group) and "group" (the ID of the corresponding shiftGroup.)
I had also tried with firestore.References of shifts in groups, that's when I was the closest to my goal, but then I was stuck when trying to sort shifts inside groups.
Anyway, with vuefire, I can easily bind shiftGroup like this:
{
data () {
return {
shiftGroup: [], // to initialize
}
},
firestore () {
return {
shiftGroup: db.collection('shiftGroup').orderBy('order'),
}
},
}
Then display the groups like this:
<ul>
<li v-for="(group, idx) in shiftGroup" :key="idx">{{group.name}}</li>
</ul>
So now time to add the shifts...
I thought I could get a reactive array of shifts for each of the groups, like that:
{
db.collection('shift').where('group', '==', group.id).orderBy('order').onSnapshot((querySnapshot) => {
this.shiftCollections[group.id] = [];
querySnapshot.forEach((doc) => {
this.shiftCollections[group.id].push(doc.data());
});
});
}
then I'd call the proper list like this:
<ul>
<li v-for="(group, idx) in shiftGroup" :key="idx">
{{group.name}}
<ul>
<li v-for="(shift, idx2) in shiftCollections[group.id]" :key="idx1+idx2">{{shift.name}}</li>
</ul>
</li>
</ul>
This is very bad code, and actually, the more I think about it, the more I think that it's just impossible to achieve.
Of course I thought of using programmatic binding like explained in the official doc:
this.$bind('documents', documents.where('creator', '==', this.id)).then(
But the first argument has to be a string whereas I need to work with dynamic data.
If anyone could suggest me a way to obtain what I described.
Thank you all very much
So I realize this is an old question, but it was in important use case for an app I am working on as well. That is, I would like to have an object with an arbitrary number of keys, each of which is bound to a Firestore document.
The solution I came up with is based off looking at the walkGet code in shared.ts. Basically, you use . notation when calling $bind. Each dot will reference a nested property. For example, binding to docs.123 will bind to docs['123']. So something along the lines of the following should work
export default {
name: "component",
data: function () {
return {
docs: {},
indices: [],
}
},
watch: {
indices: function (value) {
value.forEach(idx => this.$bind(`docs.${idx}`, db.doc(idx)))
}
}
}
In this example, the docs object has keys bound to Firestore documents and the reactivity works.
One issue that I'm trying to work through is whether you can also watch indices to get updates if any of the documents changes. Right now, I've observed that changes to the Firestore documents won't trigger a call to any watchers of indices. I presume this is related to Vue's reactivity, but I'm not sure.

Vuetify Autocomplete minimum character before filtering

Is there a property or a method that will prevent Vuetify Autocomplete to filter items to display until a certain condition is met, such as 3 character typed? I have a basic solution but I really hope that there is another solution. I don't want anything to show until the end user types a minimum of three characters. I have a solutions such as:
watch: {
search (val) {
if(val.length > 2){
this.minimumCharacter = 'show'
}else{
this.minimumCharacter = 'null'
}
And in my HTML:
<template
v-if="minimumCharacter === 'show'"
slot="item"
slot-scope="{ item, tile }"
>
Surely the Autocomplete has a property somewhere that will handle this. When you have thousands and thousands of records you don't really want everything to show as soon as you type one character. But I've search https://vuetifyjs.com/en/components/autocompletes#autocomplete and unless they call it something that I can not relate its not there.
Surely the Autocomplete has a property somewhere that will handle this. When you have thousands and thousands of records you don't really want everything to show as soon as you type one character. But I've search https://vuetifyjs.com/en/components/autocompletes#autocomplete and unless they call it something that I can not relate its not there.
I cannot find such property, but for me works fine this variant:
watch: {
search (val) {
if(val.length > 2){
//search code
}
P.S. Filter starts working after search, so it doesn't solve current task to prevent search.
You can use filter prop to implement your own filter function that always returns false if text length is less then 3:
(item, queryText, itemText) => {
const hasValue = val => val != null ? val : ''
const text = hasValue(itemText)
const query = hasValue(queryText)
if(queryText < 3) return false;
return text.toString()
.toLowerCase()
.indexOf(query.toString().toLowerCase()) > -1
}

How to alphabetically sort a list of options in Vue.js / Buefy form?

Currently I display a list of hotels for each city in a Vue.js / Buefy form using:
<option
:value="h['#attributes'].Name"
v-for="h in cities[form.cities[i].index].Hotels.Hotel"
:key="cities[form.cities[i].index].Hotels.Hotel.Name"
v-if="isArray(form.cities[i].index)"
v-text="h['#attributes'].Name"></option>
What should I add to sort them alphabetically? I'm at loss, as I don't know Vue / Buefy so well and I'm modifying a code somebody else wrote.
Thanks!
It is important to understand what your code is doing so that you know where you need to make changes.
Your loop v-for is iterating over your array cities[form.cities[i].index].Hotels.Hotel (the naming seems odd to me).
Within this array, there is a key #attributes which holds an object with a key Name, which is probably what you want to use for sorting.
Normally I would go with computed properties for these things but since you have the array based on a parameter (form.cities[i].index) I am not sure that would work so easily. So instead you can use a method to get a sorted version of your array. In your Vue instance, add the following to the "methods" property:
methods: {
sortedHotels: function(hotels) {
tmp = this.hotels.slice(0);
tmp.sort(function(a,b) {
return (a['#attributes'].Name > b['#attributes'].Name) ? 1 : ((b['#attributes'].Name> a['#attributes'].Name) ? -1 : 0);
});
return tmp;
},
},
Then, instead of looping through the normal array, you loop through the result of the function call of that array:
<option
:value="h['#attributes'].Name"
v-for="h in sortedHotels(cities[form.cities[i].index].Hotels.Hotel)"
:key="cities[form.cities[i].index].Hotels.Hotel.Name"
v-if="isArray(form.cities[i].index)"
v-text="h['#attributes'].Name"></option>