I have a price slider and two inputs for min and max price. I have a small bug. When I try to enter another value in the min price field and start erasing the previous one, it immediately changes to 50 (the least value of a price slider), and only after the input "switches", I can enter a new min price.
I use a multi-range slider component by developergovindgupta.
This is how this code's working at the moment: https://streamable.com/r9rfsh.
Here's my price slider with inputs:
<script setup>
import { computed, ref, reactive, watch } from "vue";
import { useVuelidate } from "@vuelidate/core";
import { minValue, maxValue } from "@vuelidate/validators";
import MultiRangeSlider from "multi-range-slider-vue";
const props = defineProps({
listPrice: {
type: Object
},
modelValue: {
type: Object
},
})
const emit = defineEmits(["update:modelValue"])
function updatePrice(e) {
valsPrices.min = e.minValue;
valsPrices.max = e.maxValue;
}
const rangeMargin = 50;
const listPrices = reactive(JSON.parse(JSON.stringify(props.listPrice)))
const valsPrices = reactive(JSON.parse(JSON.stringify(props.modelValue)))
const rules = computed(() => ({
min: {
minValue: minValue(listPrices.min),
maxValue: maxValue(
((valsPrices.max - rangeMargin) >= listPrices.min) ?
valsPrices.max - rangeMargin :
listPrices.min
)
},
max: {
minValue: minValue(
((valsPrices.min + rangeMargin) <= listPrices.max) ?
valsPrices.min + rangeMargin :
listPrices.max
),
maxValue: maxValue(listPrices.max)
},
}))
const v = useVuelidate(rules, valsPrices)
function cleanMinPrice() {
if (v.value.min.minValue.$invalid) {
valsPrices.min = listPrices.min
console.log(v.value.min.$model)
}
if (v.value.min.maxValue.$invalid) {
valsPrices.min = valsPrices.max - rangeMargin
}
}
function cleanMaxPrice() {
console.log(valsPrices.max)
if (v.value.max.minValue.$invalid) {
valsPrices.max = valsPrices.min + rangeMargin
}
if (v.value.max.maxValue.$invalid) {
valsPrices.max = listPrices.max
}
}
watch(valsPrices, async (val) => {
emit("update:modelValue", val);
}, {
deep: true,
immediate: true
})
</script>
<template>
<h6>Price</h6>
<div class="d-flex justify-content-between">
<div class="d-flex align-items-center">
<label for="minPrice" class="form-label m-0">Min: </label>
<input type="number"
class="form-control form-control-sm"
id="minPrice"
v-model="v.min.$model"
@blur="cleanMinPrice">
</div>
<div class="d-flex align-items-center">
<label for="maxPrice" class="form-label m-0">Max: </label>
<input type="number"
class="form-control form-control-sm"
id="maxPrice"
v-model="v.max.$model"
@blur="cleanMaxPrice">
</div>
</div>
<MultiRangeSlider
baseClassName="multi-range-slider"
:min="props.listPrice.min"
:max="props.listPrice.max"
:minValue="valsPrices.min"
:maxValue="valsPrices.max"
:ruler="false"
:rangeMargin="rangeMargin"
@input="updatePrice"
/>
</template>
This is how the component is used inside a filter component:
<script setup>
import {reactive, watch} from "vue";
import PriceSlider from "./PriceSlider.vue";
const props = defineProps({
filterList: Object,
modelValue: Object,
loading: Boolean,
})
const emit = defineEmits(["filter", "update:modelValue"])
let filterVals = reactive(JSON.parse(JSON.stringify(props.modelValue)))
watch(
filterVals,
(value) => {
emit("update:modelValue", value);
},
{ deep: true, immediate: true }
)
</script>
<template>
<section class="col-3">
...
<div class="mb-3">
<h3 class="fs-3">Price</h3>
<PriceSlider
:listPrice="filterList.price"
v-model="filterVals.price" />
</div>
</section>
</template>
These are the filterList
and filterValues
I pass to the slider in Products.vue
- the page where the products are displayed. filterList
contains all possible filter values, like all colors, categories and the least and greatest values of the price slider. filterVals
contains filter options selected by a user.
const filterList = reactive({
price: {
min: 50,
max: 900
}
})
const filterValues = reactive({
price: {
min: 80,
max: 600
}
})
They're declared inside the Products.vue
component. This is how the filter component is used inside the products page.
<script setup>
import HeaderOther from "../components/layout/HeaderOther.vue";
import Filter from "../components/Products/Filter.vue";
import { reactive } from "vue";
const filterList = reactive({
price: {
min: 50,
max: 900
}
})
const filterValues = reactive({
price: {
min: 80,
max: 600
}
})
function getFilterResults() {
console.log(filterValues.value)
}
</script>
<template>
<div class="container-fluid">
<HeaderOther></HeaderOther>
<div class="row mw-2000 mx-5 my-0">
<Filter :filterList="filterList"
v-model="filterValues"
@filter="getFilterResults" />
</div>
</div>
</template>
I've found out that the issue lies in the function that updates min and max price when the slider's thumb is dragged:
<script setup>
...
function updatePrice(e) {
valsPrices.min = e.minValue;
valsPrices.max = e.maxValue;
}
...
</script>
<template>
...
<MultiRangeSlider
baseClassName="multi-range-slider"
:min="props.listPrice.min"
:max="props.listPrice.max"
:minValue="valsPrices.min"
:maxValue="valsPrices.max"
:ruler="false"
:rangeMargin="rangeMargin"
@input="updatePrice"
/>
</template>
But I don't know what to do to solve this problem. Thanks in advance!
UPDATE 1
Unfortunately, after changing the event, another problem appeared. valsPrice.min
and valsPrice.max
don't change when the thumb is dragged. It looks like this right now: https://streamable.com/vg8feh.
This is the MultiRangeSlider component now:
<MultiRangeSlider
baseClassName="multi-range-slider"
:min="props.listPrice.min"
:max="props.listPrice.max"
:minValue="valsPrices.min"
:maxValue="valsPrices.max"
:ruler="false"
:rangeMargin="rangeMargin"
@keyDown="updatePrice"
/>
The solution for me was using the <InputNumber>
PrimeVue component. It updates and validates the value in its v-model
on the blur
event, not on the input
one. It doesn't mean one should use specifically this component to solve a similar issue, the main thing here is that the value in the v-model
should be updated and validated, not only validated, on blur.
Side note: Vuelidate turned out to be redundant in my case.