so i make an signup and address form for every user and i want every time the user is connected and navigate to the profile page, he will edit his details.
now i have async autocomplete from api that get for me all the items in object format,
so i tried to give the v-model an default value but it didn't change, i guess there is supposed to be connection between the items to the v-model, so i tried to fake the async search and get the items but still couldn't see the default value.
i don't care if the value wont be in the data i just want to see it visual
<script>
export default {
props: {
cmd: {
type: String
},
itemText: {
type: String
},
itemValue: {
type: String
},
label: {
type: String
},
city: {
type: Number
},
street: {
type: Number
},
type: {
type: String
},
defaultValue: {
type: String || Number
}
},
data() {
return {
loading: false,
items: [],
search: null,
select: null
};
},
watch: {
search(val) {
val && val !== this.select && this.querySelections(val);
},
select(val) {
if (val !== this.defaultValue) {
this.$emit("selected", { value: val[this.itemValue], text: val[this.itemText] });
}
}
},
async mounted() {
const defaultSelected = {};
defaultSelected[`${this.itemText}`] = this.defaultValue ? this.defaultValue.value : this.defaultValue;
defaultSelected[`${this.itemValue}`] = this.defaultValue ? this.defaultValue.id : this.defaultValue;
await this.querySelections(defaultSelected[`${this.itemText}`]);
console.log(this.items);
// this.select = defaultSelected[`${this.itemText}`];
},
methods: {
async querySelections(v) {
this.loading = true;
// Simulated ajax query
const data = (await this.$services.SearchService.searchAddress(v, this.cmd, this.city, this.street)).data;
console.log(data);
if (this.type === "city") {
this.items = data.Data[`CitiesRes`];
}
if (this.type === "street") {
this.items = data.Data[`StreetsRes`];
}
if (this.type === "streetNumber") {
this.items = data.Data["NumbersRes"];
}
this.loading = false;
},
asyncinsertDefualtData() {}
}
};
</script>
<template>
<v-autocomplete
v-model="select"
:loading="loading"
:items="items"
:search-input.sync="search"
:item-text="itemText"
:item-value="itemValue"
:value="defaultValue"
return-object
cache-items
flat
hide-no-data
hide-details
solo
:label="label"
></v-autocomplete>
</template>
Related
by the way this question very similar on Vue algolia autosuggest on select showing [Object object]
but i still did not solve here is my debug or code
<b-form-group
label="Name Agen"
label-for="vi-agen-name"
>
<span v-text="form.agen.name_agen" />
<vue-autosuggest
id="vi-agen-name"
v-model="form.agen.name_agen"
:suggestions="[{ data: form.agen.agens }]"
:limit="7"
:input-props="{
id: 'autosuggest__input',
class: 'form-control',
placeholder: 'Name Agen',
}"
#selected="onSelectedFrom"
#input="searchForm($event, 'agen/', 'agen', 'name_agen')"
>
<template slot-scope="{suggestion}">
<span class="my-suggestion-item">{{ suggestion.item.name_agen }}</span>
</template>
</vue-autosuggest>
</b-form-group>
my problem:
i have on typing yogi on form input
select item that show on suggesstion : [ { data : [ { name_agen : 'Yogi' .....
<span v-text="form.agen.name_agen" /> // output : Yogi
form input // output : yogi
but when i tried to type again on form input yogiabc
thats not show any suggestion so i remove by backspace so the input now is yogi
then i tried select again
the unexpected on twice select :
<span v-text="form.agen.name_agen" /> // output : Yogi // why this result is string
form input // output : object-object // but on form input is an object-object ?
function code:
async onSelectedFrom(option) {
const model = this.form.currentModel
const fieldSuggest = this.form.currentFieldSuggest
const currentLoadData = this.form[`${model}`][`${model}s`]
const currentField = this.form[`${model}`][`${fieldSuggest}`]
this.form[`${model}`] = {
isNew: false,
[`${model}s`]: currentLoadData,
...option.item,
}
console.log('selected', this.form[`${model}`], 'Name Agen:', currentField)
},
searchForm(keyword, uri, model, currentSuggest) {
this.form.currentModel = model
this.form.currentFieldSuggest = currentSuggest
if (keyword) {
clearTimeout(this.timeoutDebounce)
this.timeoutDebounce = setTimeout(() => {
useJwt.http.get(`${uri}`, { params: { keyword, single_search: true } })
.then(response => {
// test debug
if (response.data.total_items === 0) {
// no data
this.form[`${model}`].isNew = true
this.form[`${model}`].user.full_name = null
this.form.isAgenAndTrans = false
this.form[`${model}`][`${model}s`] = []
console.log('no data show', this.form[`${model}`])
} else {
// this.form[`${model}`].isNew = false
this.form.isAgenAndTrans = false
this.form[`${model}`][`${model}s`] = response.data[`${model}s`]
}
}).catch(e => {
this.form[`${model}`].isNew = true
this.form[`${model}`].user.full_name = null
this.form.isAgenAndTrans = false
this.form[`${model}`][`${model}s`] = []
})
}, 300)
}
},
then these the data
data() {
return {
payload: [],
form: {
currentModel: '',
currentFieldSuggest: '',
isAgenAndTrans: false,
agen: {
isNew: true,
id: 0,
name_agen: '',
dm_personal_id: 0,
user: {
full_name: '',
},
agens: [],
},
}
}
}
I'm using the Vue Currency Input to an input on the app that I'm working on, I'm not sure why I have this weird issue when the old prop comes back even thought no change was fired. The behaviour is: I edit the price and save, but when I click to be editable again the old price pops up.
This is my code:
<template>
<div>
<input ref="input" :value="val" v-currency="options" allow-negative="false" class="editableValue" #change="change" #keydown.enter.prevent>
</div>
</template>
<script>
import { parse } from "vue-currency-input";
import { UTILS } from "#/helpers/utils.js";
export default {
name: "EditableValue",
data(){
return {
val: this.value
}
},
props: {
currency: {
type: Object
},
value: {
type: Number
}
},
computed: {
options() {
return {
autoDecimalMode: true,
currency: {prefix: this.currency.currency + " "},
distractionFree: false
};
},
},
methods: {
change(){
console.log('chamou')
let newValue = parse(this.$refs.input.value, this.options);
this.val = newValue;
this.$emit('finishEditPrice', newValue)
},
},
watch: {
value(current) {
let inputValue = this.$refs.input.value;
let formattedValue = UTILS.getFormattedPrice(current, this.currency);
if (inputValue != formattedValue) {
this.val = formattedValue;
this.$refs.input.value = formattedValue;
}
},
},
updated(){
// if (inputValue != formattedValue) {
// this.val = formattedValue;
// this.$refs.input.value = formattedValue;
// }
}
};
</script>
The updated() and watch was a trial to change this behaviour but no luck. I'm using this version "vue-currency-input": "^1.22.6". Does anyone have any idea how to solve it? Thanks!
the expand/collapse part of this works just fine.
Right now I am using javascript startInterval() to reload the table every 2 seconds. Eventually this will be moving to web sockets.
In general, as part of the table load/reload, the system checks to see if it should display the icon " ^ " or " v " in the details column by checking row.detailsShowing, this works fine.
getChevron(row, index) {
if (row.detailsShowing == true) {
return "chevronDown";
}
return "chevronUp";
}
When the user selects the " ^ " icon in the relationship column, #click=row.toggleDetails gets called to expand the row and then the function v-on:click="toggleRow(row)" is called to keep track of which row the user selected. This uses a server side system generated guid to track.
Within 2 seconds the table will reload and the row collapses. On load/reload, in the first column it loads, relationship, I call a function checkChild(row), to check the row guid against my locally stored array, to determine if this is a row that should be expanded on load.
<template #cell(relationship)="row"> {{checkChild(row)}} <\template>
if the row guid matches one in the array I try setting
checkChild(row){
var idx = this.showRows.indexOf( row.item.id);
if(idx > -1){
row.item.detailsShowing = true;
row.rowSelected = true;
row.detailsShowing == true
row._showDetails = true;
}
}
and I am able to see that i have found match, but none of those variables set to true keeps the expanded row open, the row always collapses on reload
anyone have any ideas as to how i can make the row(s) stay open on table reload?
The issue with your code is because of a Vue 2 caveat. Adding properties to objects after they've been added to data will not be reactive. To get around this you have to utilize Vue.set.
You can read more about that here.
However, calling a function like you are doing in the template seems like bad practice.
You should instead do it after fetching your data, or use something like a computed property to do your mapping.
Here's two simplified examples.
Mapping after API call
{
data() {
return {
items: [],
showRows: []
}
},
methods: {
async fetchData() {
const { data } = await axios.get('https://example.api')
foreach(item of data) {
const isRowExpanded = this.showRows.includes(item.id);
item._showDetails = isRowExpanded;
}
this.items = data;
}
}
}
Using a computed
{
computed: {
// Use `computedItems` in `<b-table :items="computedItems">`
computedItems() {
const { items, showRows } = this;
return items.map(item => ({
...item,
_showDetails: .showRows.includes(item.id)
}))
}
},
data() {
return {
items: [],
showRows: []
}
},
methods: {
async fetchData() {
const { data } = await axios.get('https://example.api')
this.items = data;
}
}
}
For a more complete example, check the snippet below.
const {
name,
datatype,
image
} = faker;
const getUser = () => ({
uuid: datatype.uuid(),
personal_info: {
first_name: name.firstName(),
last_name: name.lastName(),
gender: name.gender(),
age: Math.ceil(Math.random() * 75) + 15
},
avatar: image.avatar()
});
const users = new Array(10).fill().map(getUser);
new Vue({
el: "#app",
computed: {
computed_users() {
const {
expanded_rows,
users
} = this;
return users.map((user) => ({
...user,
_showDetails: expanded_rows[user.uuid]
}));
},
total_rows() {
const {
computed_users
} = this;
return computed_users.length;
}
},
created() {
this.users = users;
setInterval(() => {
users.push(getUser());
this.users = [...users];
}, 5000);
},
data() {
return {
per_page: 5,
current_page: 1,
users: [],
fields: [{
key: "avatar",
class: "text-center"
},
{
key: "name",
thClass: "text-center"
},
{
key: "personal_info.gender",
label: "Gender",
thClass: "text-center"
},
{
key: "personal_info.age",
label: "Age",
class: "text-center"
}
],
expanded_rows: {}
};
},
methods: {
onRowClicked(item) {
const {
expanded_rows
} = this;
const {
uuid
} = item;
this.$set(expanded_rows, uuid, !expanded_rows[uuid]);
}
}
});
<link href="https://unpkg.com/bootstrap#4.5.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.js"></script>
<script src="https://unpkg.com/faker#5.5.3/dist/faker.min.js"></script>
<div id="app" class="p-3">
<b-pagination v-model="current_page" :per-page="per_page" :total-rows="total_rows">
</b-pagination>
<h4>Table is refresh with a new item every 5 seconds.</h4>
<h6>Click on a row to expand the row</h6>
<b-table :items="computed_users" :fields="fields" bordered hover striped :current-page="current_page" :per-page="per_page" #row-clicked="onRowClicked">
<template #cell(avatar)="{ value }">
<b-avatar :src="value"></b-avatar>
</template>
<template #cell(name)="{ item: { personal_info: { first_name, last_name } }}">
{{ first_name }} {{ last_name }}
</template>
<template #row-details="{ item }">
<pre>{{ item }}</pre>
</template>
</b-table>
</div>
Well a have some value in remote storage (lets say x) and b-form-checkbox that should control this value. I want to inform user if value actually changed on storage and time when it happens.
So basically I want:
When user check/uncheck b-form-checkbox I want to change state of b-form-checkbox, send async request to the remote storage and show some b-spinner to indicate that state isn't actually changed yet.
When I receive answer from remote storage:
if change was successful just hide b-spinner.
if change was not successful (timeouted, error on server, etc) I want to change b-form-checkbox state back (since value actually doesn't changed on storage) and hide b-spinner
What is the silliest way to do int using Vue + Vuex?
Currently I'm doing it this way:
xChanger.vue:
<template>
<b-form-checkbox v-model="xComp" switch>
{{xComp ? 'On' : 'Off'}}
<b-spinner v-if="!xSynced"/>
</b-form-checkbox>
</template>
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
export default {
name: 'XChanger',
computed: {
...mapState(['x']),
...mapGetters(['xSynced']),
xComp: {
get() { return x.local },
set(value) {
if (value != this.x.local) {
this.setX(value)
}
},
},
},
methods: {
...mapActions(['setX']),
},
}
</script>
main.js
import Vuex from 'vuex'
import Axios from 'axios'
const store = new Vuex.Store({
state: {
x: {
remote: null,
local: null
},
getters: {
xSynced(state) {
state.x.local === state.x.remote
}
},
actions: {
async setX(store, value) {
store.state.x.local = value
try {
let response = await Axios.post('http://blah.blah/setX', {x: value});
if (response.status == 200) {
store.state.x.remote = value
}
} catch (error) {
store.state.x.local = !value
}
}
},
mutations: {
setX(state, value) {
state.x.local = value
state.x.remote = value
}
}
},
})
But it is too verbose for just one value to be controlled (especially computed property xComp). I'm sure that such a simple template should be already solved and has more simple way to implement.
Here is an example:
<template>
<b-form-checkbox v-model="x.local" switch>
{{x.local ? 'On' : 'Off'}}
<b-spinner v-if="saving"/>
</b-form-checkbox>
</template>
<script>
export default
{
name: 'XChanger',
data: () => ({
x:
{
local: false,
remote: false,
},
saving: false,
}),
watch:
{
'x.local'(newValue, oldValue)
{
if (newValue !== oldValue && newValue !== this.x.remote)
{
this.updateRemote(newValue);
}
}
}
methods:
{
async updateRemote(value)
{
try
{
this.saving = true;
const response = await Axios.post('http://blah.blah/setX', {x: value});
if (response.status == 200)
{
this.x.remote = value;
}
else
{
this.x.local = this.x.remote;
}
}
catch (error)
{
this.x.local = this.x.remote;
}
this.saving = false;
}
},
}
</script>
I'm trying to filter the results of a table by using vue-multiselect. I can see the selected values in the VUE dev tools as a part of multiselect component. How do I use these values to be used in filter() function to get the filtered table results.
Below you can see my JS script implementation and Template multiselect implementation as well.
JS Script
export default {
data: () => ({
policies: [],
selectedValues: [],
options: [],
}),
methods: {
filterByStatus: function({ label, value }) {
return this.policies.filter(data => {
let status= data.status.toLowerCase().match(this.selectedValues.toLowerCase());
},
Template
<multiselect
v-model="selectedValues"
:options="options"
:multiple="true"
label="label"
track-by="label"
placeholder="Filter by status"
#select="filterByStatus"
></multiselect>
Your select component is using the prop :multiple="true", this means the bound value selectedValues, with v-model, will return an array of policy objects.
Instead of using a filterByStatus function in the methods component options, create two computed properties.
One that computes an array of the selected policies statuses and another one that computes the filtered array of policies you want to display.
Script:
computed: {
selectedStatuses() {
const statuses = []
for (const { status } of this.selectedValues) {
statuses.push(status.toLowerCase())
}
return statuses
},
filteredPolicies() {
if (this.selectedStatuses.length === 0) {
return this.policies
}
const policies = []
for (const policy of this.policies) {
if (this.selectedStatuses.includes(policy.status.toLowerCase())) {
policies.push(policy)
}
}
return policies
}
}
Template:
<multiselect
v-model="selectedValues"
:options="options"
:multiple="true"
label="label"
track-by="label"
placeholder="Filter by status"
></multiselect>
Example:
Vue.config.productionTip = Vue.config.devtools = false
new Vue({
name: 'App',
components: {
Multiselect: window.VueMultiselect.default
},
data() {
return {
policies: [{
label: 'Policy A',
status: 'enabled'
}, {
label: 'Policy B',
status: 'disabled'
}, {
label: 'Policy C',
status: 'Deprecated'
}],
selectedValues: [],
options: [{
label: 'Enabled',
status: 'enabled'
}, {
label: 'Disabled',
status: 'DISABLED'
}, {
label: 'Deprecated',
status: 'DePrEcAtEd'
}]
}
},
computed: {
selectedStatuses() {
const statuses = []
for (const {
status
} of this.selectedValues) {
statuses.push(status.toLowerCase())
}
return statuses
},
filteredPolicies() {
if (this.selectedStatuses.length === 0) {
return this.policies
}
const policies = []
for (const policy of this.policies) {
if (this.selectedStatuses.includes(policy.status.toLowerCase())) {
policies.push(policy)
}
}
return policies
}
},
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vue-multiselect#2.1.0"></script>
<link rel="stylesheet" href="https://unpkg.com/vue-multiselect#2.1.0/dist/vue-multiselect.min.css">
<div id="app">
<multiselect v-model="selectedValues" :options="options" :multiple="true" label="label" track-by="label" placeholder="Filter by status"></multiselect>
<pre>Policies: {{ filteredPolicies}}</pre>
</div>
It is better to keep the filter function inside computed.
computed:{
filterByStatus: function ({label, value}) {
return this.policies.filter((data) => {
return data.status && data.status.toLowerCase().includes(this.selectedValues.toLowerCase())
});
}
}
Using the filterByStatus in the template section will render the result in real time.
<div>{{filterByStatus}}</div>
you can use watch on selectedValues when any change or selection :
watch:{
selectedValues: function(value){
this.policies.filter(data => {
let status= data.status.toLowerCase().match(this.selectedValues.toLowerCase());
}
}
This how I did in one of my vue3 projects, where I had multiple dropdowns with multi-items can be selected to filter and with a text input box:
const filterByInput = (item: string) =>
item.toLowerCase().includes(state.searchInput.toLowerCase());
const filteredItems = computed(() => {
let fItems = JSON.parse(JSON.stringify(props.items));
if (state.searchInput) {
fItems = fItems.filter((item) => filterByInput(item.title));
}
if (state.filterByState.length) {
fItems = fItems.filter((item) => state.filterByState.includes(item.state));
}
if (state.filterByType.length) {
fItems = fItems.filter((item) => state.filterByType.includes(item.typeId));
}
if (state.filterByPublishing !== null) {
fItems = fItems.filter((item) => item.published === state.filterByPublishing);
}
return fItems;
// NOTE: other options that you can try (should be placed above `return`)
// const filterMultiSelect = (fItems, selectedItems, key) => {
// // OPT-1: foreach
// const arr = [];
// fItems.forEach((item) => {
// if (selectedItems.includes(item[key])) {
// arr.push(item);
// }
// });
// return arr;
// // OPT-2: filter
// return fItems.filter((item) => selectedItems.includes(item[key]));
// };
// if (state.filterByState.length) {
// fItems = filterMultiSelect(fItems, state.filterByType, 'typeId');
// }
});
You can find full code in this gist