Quasar q-select display nothing when I choose one of the options - vue.js

New to Quasar, and I try to create a q-select that getting as options a list of objects. So far, I achieved to display the options in the dropdown, but when I select an element, the q-select remains empty although the page component data value is filled properly.
See the following git for more info:
My q-select implementation is like this:
<template>
<!-- ... -->
<q-select
v-model="parentCategory"
clearable
use-input
input-debounce="350"
label="Parent Category"
:options="businessCategoriesToSelect"
:option-label="opt => Object(opt) === opt && 'title' in opt ? opt.title : null"
:option-value="opt => Object(opt) === opt && '#id' in opt ? opt['#id'] : null"
:display-value="Object(parentCategory) === parentCategory && 'title' in parentCategory ? parentCategory.title : null"
emit-value
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
No Business Categories Found
</q-item-section>
</q-item>
</template>
</q-select>
<!-- ... -->
</template>
<script>
// ...
export default {
// ...
data() {
return {
// ...
parentCategory : null,
// ...
businessCategoriesToSelect: [],
// ...
};
},
// ...
}
</script>
The objects loaded in the businessCategoriesToSelect have the following form:
And when I select one of the options in the q-select item the data property that is responsible to hold the selected value it has a value like this:
But although all seems to be OK with what I did, the q-select still don't like to display the proper value.
Any idea on how to solve that issue?

I answer to my self just to provide my solution in order to help any other person has the same problem with me.
Finally I solved the problem by using the slot selected-item. The final solution is like that:
<q-select
v-model="parentCategory"
clearable
use-input
input-debounce="350"
label="Parent Category"
:options="businessCategoriesToSelect"
:option-label="opt => Object(opt) === opt && 'title' in opt ? opt.title : null"
:option-value="opt => Object(opt) === opt && '#id' in opt ? opt['#id'] : null"
:display-value="Object(parentCategory) === parentCategory && 'title' in parentCategory ? parentCategory.title : null"
emit-value
map-options
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
No Business Categories Found
</q-item-section>
</q-item>
</template>
<template v-slot:selected-item="scope">
{{ scope.opt.title }}
</template>
</q-select>

Related

How to make single property in array reactive when using `ref` instead of `reactive`?

I have a component that displays rows of data which I want to toggle to show or hide details. This is how this should look:
This is done by making the mapping the data to a new array and adding a opened property. Full working code:
<script setup>
import { defineProps, reactive } from 'vue';
const props = defineProps({
data: {
type: Array,
required: true,
},
dataKey: {
type: String,
required: true,
},
});
const rows = reactive(props.data.map(value => {
return {
value,
opened: false,
};
}));
function toggleDetails(row) {
row.opened = !row.opened;
}
</script>
<template>
<div>
<template v-for="row in rows" :key="row.value[dataKey]">
<div>
<!-- Toggle Details -->
<a #click.prevent="() => toggleDetails(row)">
{{ row.value.key }}: {{ row.opened ? 'Hide' : 'Show' }} details
</a>
<!-- Details -->
<div v-if="row.opened" style="border: 1px solid #ccc">
<div>opened: <pre>{{ row.opened }}</pre></div>
<div>value: </div>
<pre>{{ row.value }}</pre>
</div>
</div>
</template>
</div>
</template>
However, I do not want to make the Array deeply reactive, so i tried working with ref to only make opened reactive:
const rows = props.data.map(value => {
return {
value,
opened: ref(false),
};
});
function toggleDetails(row) {
row.opened.value = !row.opened.value;
}
The property opened is now fully reactive, but the toggle doesn't work anymore:
How can I make this toggle work without making the entire value reactive?
The problem seems to come from Vue replacing the ref with its value.
When row.opened is a ref initialized as ref(false), a template expression like this:
{{ row.opened ? 'Hide' : 'Show' }}
seems to be interpreted as (literally)
{{ false ? 'Hide' : 'Show' }}
and not as expected as (figuratively):
{{ row.opened.value ? 'Hide' : 'Show' }}
But if I write it as above (with the .value), it works.
Same with the if, it works if I do:
<div v-if="row.opened.value">
It is interesting that the behavior occurs in v-if and ternaries, but not on direct access, i.e. {{ rows[0].opened }} is reactive but {{ rows[0].opened ? "true" : "false" }} is not. This seems to be an issue with Vue's expression parser. There is a similar problem here.

Vue-multiselect prevent selecting any items when using Single select (object)

I'm using Vue-multiselect 2.1.4
It works like a charm when I use single select with array options. But in case of using single select with array of objects, all items are green and they are not selectable! (They have "is-selected" class)
To clarify the problem, I used the sample code from the project website and replace the options with my data.
<multiselect v-model="value" deselect-label="Can't remove this value"
track-by="name" label="name" placeholder="Select one"
:options="options" :searchable="false" :allow-empty="false">
<template slot="singleLabel" slot-scope="{ option }">
<strong>{{ option.name }}</strong> is written in
<strong> {{ option.language }}</strong>
</template>
</multiselect>
const config = {
data() {
return {
value: null,
options: []
}
},
async mounted() {
await this.getTerminals();
},
methods: {
async getTerminals() {
await window.axios.get("/api/Operation/GetTerminals")
.then(resp => {
this.$data.options = resp.data;
})
.catch(err => {
console.error(err);
});
},
}
};
const app = Vue.createApp(config);
app.component('Multiselect', VueformMultiselect);
app.mount('#app');
In case of array of objects, first you need to populate the values in object and then push the object in options array. And there will be few changes in the template as well. For example if your object is like this, following will work:
data(){
return{
value: null,
option: {
value: "",
name: "",
icon: "",
},
options: [],
}
},
methods: {
getData(){
//call your service here
response.data.list.forEach((item, index) => {
self.option.value = item.first_name + item.last_name;
self.option.name = item.first_name + " " + item.last_name;
self.option.icon =
typeof item.avatar !== "undefined" && item.avatar != null
? item.avatar
: this.$assetPath + "images/userpic-placeholder.svg";
self.options.push({ ...self.option });
});
}
}
Then in the template fill the options like this:
<Multiselect
v-model="value"
deselect-label="Can't remove this value"
track-by="value"
label="name"
:options="options"
:searchable="false"
:allow-empty="false"
>
<template v-slot:singlelabel="{ value }">
<div class="multiselect-single-label">
<img class="character-label-icon" :src="value.icon" />
{{ value.name }}
</div>
</template>
<template v-slot:option="{ option }">
<img class="character-option-icon" :src="option.icon" />
{{ option.name }}
</template>
</Multiselect>
Call your getData() function in created hook.
For me the solution was to use the "name" and "value" keys for my object. Anything else and it doesn't work (even if they use different keys in the documenation). This seems like a bug, but that was the only change I needed to make.

how to create dynamic input filters for columns in bootstrap-vue 2.0

I'm trying to create some dynamic filters in bootstrap-vue 2.0.0-rc.11 for the bootstrap-table columns
In my example I did this
<b-table class="io-store-list-table table-striped" show-empty hover stacked="md" :items="stores" :fields="storeFields" :current-page="currentPage" :per-page="perPage" :filter="filter" :sort-by.sync="sortBy" :sort-desc.sync="sortDesc" :sort-direction="sortDirection" #filtered="onFiltered" empty-filtered-text="{l s='There are no records matching your request' mod='ioweb_slocator'}">
<template slot="top-row" slot-scope="data">
<th v-for="field in fields">
<input v-if="field.filterable" type="text" #click.stop value="" />
</th>
</template>
<template v-for='field in formatted' :slot='field' slot-scope='row'>
<span v-html='row.value'></span>
</template>
</b-table>
<b-row>
<b-container>
<b-col xs="12" class="my-1 io-pagination">
<b-pagination :total-rows="totalRows" :per-page="perPage" v-model="currentPage" class="my-0"/>
</b-col>
</b-container>
</b-row>
</b-table>
Which renders a table like this
Now I'm attempting to filter the items based on what value is entered in each column. For example if I type mypartnername at the input box below Partner column, I would like to dynamically filter the items based on the row key called partner but I'm not sure how to approach this.
Based on the accepted answer I was able to create a structure that helped me like this.
Step 1:
Create a property filteredResults and set it equal to my unfiltered items property
let app = new Vue({
el: "#app",
data() {
return {
...
stores: stores,
groups: groups,
totalRows: stores.length,
filters: [],
loop: 0,
filteredResults: stores,
}
},
Step 2
Used filteredResults in the :items slot
Step 3
Create a single function to set the filters
methods: {
...
setFilter(property, value) {
console.log("Filtering");
this.filters[property] = {};
this.filters[property].value = value;
this.filteredResults = this.stores.filter(item => {
let keep = true;
// This is a basic equality filter. What I did in the actual code was to have an object with filter functions for each key. If a key was missing, it defaulted to straight equality.
this.fieldKeys.forEach(key => {
keep =
keep &&
(this.filters[key] === undefined || this.filters[key].value === undefined || this.filters[key].value === "" || item[key].match(this.filters[key].value));
});
return keep;
});
},
},
what about something like this? I dont know if i got your problem right.
<input v-if="field.filterable" #onchange="setFilter(field.property, $event.target.value)" type="text" #click.stop />
in the setFilter function you could do something like this:
setFilter(property, value) {
this.filters[property].value = value
}
get your results with a computed property
filteredResults(){
return users = this.users.filter((item) => {
for (var key in this.filters) {
if (item[key].value === undefined || item[key].value != filter[key])
return false;
}
return true;
}
}
I dont know if it works, couldnt test it. I am curious about the solution here.

vue cannot acces object props

i do a find method in an array to search for the object. I get back the object, but i can't acces the props in that object. The nuxt error thrower says the following:
Cannot read property 'value' of undefined
my code:
<v-data-table :headers="datatable.headers" :items="datatable.items" class="elevation-1">
<template v-slot:body="{ items }">
<tr v-show="inMenu.find(menuItem => menuItem.name == item.name.toLowerCase()).value == true" v-for="(item, index) in items" :key="index">
<td>{{item.name}}</td>
<!-- problem is in the <v-text-field> v-model -->
<td><v-text-field v-model="settings.adminIcons.find(menuItem => menuItem.key == item.name.toLowerCase()).value" label="icon"></v-text-field></td>
</tr>
</template>
</v-data-table>
the weird thing is, that if i do this:
<!-- works -->
<v-text-field v-model="settings.adminIcons.find(menuItem => menuItem.key == 'evenementen').value" label="icon">
instead of this:
<!-- does not work -->
<!-- item.name.toLowerCase() == 'evenementen' -->
<v-text-field v-model="settings.adminIcons.find(menuItem => menuItem.key == item.name.toLowerCase()).value" label="icon">
it will not work. but item.name.toLowerCase() is the same as 'evenementen'.
any solution?
It's giving error because in find it doesn't find any single item its return noting and you are trying to access .value.
// Error
//console.log(settings['adminIcons'].find(menuItem => menuItem.key == "env1").value)
<script>
const array1 = [5, 12, 8, 130, 44];
const settings = {
'adminIcons':[
{"key":"env",
"value":"value1"}
]
}
const found = array1.find(element => element > 10);
console.log(settings['adminIcons'].find(menuItem => menuItem.key == "env").value);
// Error
//console.log(settings['adminIcons'].find(menuItem => menuItem.key == "env1").value);
</script>
Basically, you are accessing the value of item where item is not available in your data model.
This code will give you a string and this is where your error came from
menuItem.key == item.name.toLowerCase()) // if item.name = 'Test' will become 'test'.
Now, the returned of it it is not already the item you are accessing in your loop originally, rather give you a result but in new data. That's why when you are accessing it's value it say's that cannot read property value of undefined.

Conditionally adding a CSS class in Vue

Just started with Vue so I can't get this simple thing working. All I'm trying to do is toggle a class based on a condition.
<button type="button"
class="btn dropdown-toggle"
v-bind:class="{ btn-default: (search.category != 'all') }">
{{ filterCategoryText || 'Category' }}
</button>
Firstly, as you discovered, you should probably remove the duplicate class definition. You can mix static and dynamic classes in the bound class definition. (If you leave the duplicate there it still works, though)
Then, you have the choice...
Object syntax
// property names will be in the class list if their values are truthy
:class="{
'btn-default': search.category != "all",
'btn' : true,
'dropdown-toggle' : true
}"
Array syntax
// an item in the array becomes a class in the class list
:class="[
search.category != 'all' ? 'btn-default':'',
'btn',
'dropdown-toggle'
]"
Simple expression
// if you only have one item, just use a simple expression
:class="search.category != 'all' ? 'btn-default':''"
Docs are here
You still could have used Object syntax for the class binding. I guess the code in you example didn't work because you didn't wrap object property name with quotes. Moreover, you have at least three options here. In such case I would always stick with objects if possible as it's usually more readable. Take a look:
new Vue({
el: '#app',
data: {
red: 'nope',
},
methods: {
toggle() {
this.red = this.red === 'yes' ? 'nope' : 'yes';
}
},
})
.is-bold {
font-weight: 900;
}
.is-red {
color: red;
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p class="is-bold" :class="{'is-red': red === 'yes'}">
Option
</p>
<p class="is-bold" :class="red === 'yes' ? 'is-red' : ''">
Option 1
</p>
<p class="is-bold" :class="[red === 'yes' ? 'is-red' : '']">
Option 2
</p>
<button #click="toggle">Toggle class</button>
</div>
jsfiddle: https://jsfiddle.net/oniondomes/06bg516h/
Figured it:
<button type="button"
:class="[(search.category) ? '' : 'btn-default', 'btn dropdown-toggle']"
{{ filterCategoryText || 'Category' }}
</button>
try this instead of v-bind:class="{ 'btn-default': search.category != 'all' }"