Why are checkboxes not reset by v-model? - vue.js

This is what i have:
Template
<div
v-for="(filter, index) in filtersList"
:key="index"
class="option-block"
>
<label
v-for="value in filter.values"
:key="value.id"
class="option-block__container"
>
{{ value.title }}
<input
type="checkbox"
v-model="filtersValues[filter.name]"
:value="value.value"
>
<span class="option-block__checkmark"></span>
</label>
</div>
And the part of my vue code:
data() {
return {
filtersList: {},
filtersValues: {}
}
},
beforeMount() {
this.loadInitData();
this.initFilters();
},
methods: {
loadInitData() {
const data = JSON.parse(this.$el.getAttribute('data-data'));
this.filtersList = data.filters;
},
initFilters() {
for (let i in this.filtersList) {
if (!this.filtersList.hasOwnProperty(i)) {
continue;
}
this.filtersValues[this.filtersList[i].name] = [];
}
}
}
It works, but when i call initFilters() method again (for reseting) checkboxes are still selected, and i don't know why.

The way you are assigning new, empty arrays to filterValues is not reactive.
If you change your initFilters to assign an entire new value to filterValues, you don't need to worry about using Vue.set(). For example
initFilters() {
this.filtersValues = this.filtersList.reduce((vals, { name }) => ({
...vals,
[ name ]: []
}), {})
}
Demo ~ https://jsfiddle.net/cjx09zwt/

Where did filter.values come from in line 2 of template?
Anyways vue would not be able to track the changes you are making (judging from the visible code)
There are some caveats to vue 2's reactivity. Check here for more info.
TLDR; you will need to declare anything you want to be made reactive in the component's data option upfront.
HTH

Related

Vue.js : Range slider with two handles

I want to create a vue js components where it contains a range slider of hours with two handles.
I use vue3 + vite.js
I tried this code to implement the components but when I drag one of handles I have an error
Code :
this is the template :
<template>
<div>
<input type="range" ref="rangeInput" v-model="rangeValue" #input="updateRange"/>
<div class="range-slider">
<div class="handle" :style="{left: leftHandle + '%'}" #mousedown="startHandleDrag(1)">
{{ formatHour(rangeValue[0]) }}
</div>
<div class="handle" :style="{left: rightHandle + '%'}" #mousedown="startHandleDrag(2)">
{{ formatHour(rangeValue[1]) }}
</div>
</div>
</div>
</template>
and this is the script :
<script>
export default {
data() {
return {
rangeValue: [8, 18],
handleDragging: 0
};
},
computed: {
leftHandle() {
return this.rangeValue[0];
},
rightHandle() {
return this.rangeValue[1];
}
},
methods: {
updateRange(event) {
const value = event.target.value;
const range = this.rangeValue;
if (this.handleDragging === 1) {
range[0] = value[0];
} else if (this.handleDragging === 2) {
range[1] = value[1];
} else {
range[0] = value[0];
range[1] = value[1];
}
this.rangeValue = range;
},
startHandleDrag(handle) {
this.handleDragging = handle;
document.addEventListener("mouseup", this.stopHandleDrag);
document.addEventListener("mousemove", this.updateRange);
},
stopHandleDrag() {
this.handleDragging = 0;
document.removeEventListener("mouseup", this.stopHandleDrag);
document.removeEventListener("mousemove", this.updateRange);
},
formatHour(value) {
return value + ":00";
}
}
};
</script>
Error :
any ideas to solve it !!!
In your startHandleDrag() and stopHandleDrag(), you bind updateRange() to the mousemove event:
document.addEventListener("mousemove", this.updateRange);
There are two issues with that:
The target of the mousemove event is the element under the cursor. This can be any element, and unless it happens to be an input, it will not have a value attribute (and if it does, it will not hold an array). If you really want to use the "mousemove" event, use the cursor coordinates like pageX or pageX.
You bind it as a function pointer (addEventListener("mousemove", this.updateRange)), and when called from the listener, this will refer to element.target. To avoid this, either use an arrow function (addEventListener("mousemove", (e) => this.updateRange(e))) or bind this (addEventListener("mousemove", this.updateRange.bind(this))).
I don't fully understand what you want to do with the handles, but my guess is that adding and removing listeners is a workaround, and you actually want to make them draggable? If so, have a look at the drag event. Hope that helps!

How to use parameters in Axios (vuejs)?

Good morning Folks,
I got an API from where I am getting the data from.
I am trying to filter that with Axios but I don`t get the result that I am expecting.
I created a search box. I created a computed filter and that I applied on the Axios.
I would like to see only the searched results in my flexboxes (apart from the last three articles as a start)
<template>
<div id="app">
<div class="search-wrapper">
<input
type="text"
class="search-bar"
v-model="search"
placeholder="Search in the titles"
/>
</div>
<paginate
ref="paginator"
class="flex-container"
name="items"
:list="filteredArticles"
>
<li
v-for="(item, index) in paginated('items')"
:key="index"
class="flex-item"
>
<div id="image"><img :src="item.image && item.image.file" /></div>
<div id="date">{{ formatDate(item.pub_date) }}</div>
<div id="title">{{ item.title }}</div>
<div id="article" v-html="item.details_en" target="blank">
Explore More
</div>
</li>
</paginate>
<paginate-links
for="items"
:limit="2"
:show-step-links="true"
></paginate-links>
</div>
</template>
<script>
import axios from "axios";
import moment from "moment";
export default {
data() {
return {
items: [],
paginate: ["items"],
search: "",
};
},
created() {
this.loadPressRelease();
},
methods: {
loadPressRelease() {
axios
.get(
`https://zbeta2.mykuwaitnet.net/backend/en/api/v2/media-center/press-release/?page_size=61&type=5`,
{ params }
)
.then((response) => {
this.items = response.data.results;
});
},
formatDate(date) {
return moment(date).format("ll");
},
openArticle() {
window.open(this.items.details_en, "blank");
},
},
computed: {
axiosParameters() {
const params = new SearchParams();
if (!this.search) {
return this.items;
}
return this.items.filter((item) => {
return item.title.includes(this.search);
});
},
},
};
</script>
Here is the basic code for implementing vue watcher along with the debounce for search functionality.
import _ from "lodash" // need to install lodash library
data() {
return {
search: "",
};
},
watch:{
search: _.debounce(function (newVal) {
if (newVal) {
// place your search logic here
} else{
// show the data you want to show when the search input is blank
}
}, 1000),
}
Explanation:
We have placed a watcher on search variable. Whenever it detects any change in search variable, it will execute the if block of code if it's value is not empty. If the value of search variable goes empty, it will execute else block.
The role of adding debounce here is, it will put a delay of 1 sec in executing the block of code, as we don't want to execute the same code on every single character input in the search box. Make sure you install and import lodash library. For more info on Lodash - Debounce, please refer here.
Note: This is not the exact answer for this question, but as it is asked by the question owner in the comment section, here is the basic example with code.

Why v-for item not reactive and not changed?

Why name 2 not changed and not reactive? What wrong?
How can I make it reactive so that when the properties of the object change, the DOM also changes?
When I delete Name 2 nothing happens
<template>
<div>
<div v-for="(item, index) in items" v-bind:key="item.id">
<p>{{item.name}}</p>
</div>
<button v-on:click="deleteItem">
Delete Name 2
</button>
</div>
</template>
<script>
export default {
data:function(){
return {
items:[
{
id: 1,
name: "Name 1"
},
{
id: 2,
name: "Name 2"
}
]
}
},
methods: {
deleteItem: function(){
this.items[1] = [];
console.log(this.items);
alert("DELETED");
}
},
created: function(){
let self = this;
setTimeout(function(){
self.items[1] = [];
}, 2000);
}
};
</script>
the reactivity in vue (2) is a little bit tricky, this link explain you how to solve this issue
https://v2.vuejs.org/v2/guide/reactivity.html#For-Arrays
Modify your delete item function. Don't set it to an empty array. Filter the array like this:
Your HTML Markup :
<button #click="deleteItem(2)">
Delete Name 2
</button>
Send the id of the item that you want to delete to the deleteItem() as an argument.
deleteItem: function(itemId){
let filtered = this.items.filter((each) => {
return each.id !== itemId;
})
this.items = filtered; //Finally mutate the items[] in data
}
You are actually assigning an empty array to item with index 1, instead of removing it.
If you want to remove the element with index 1 simply use splice() and Vue will automatically react to it:
this.items.splice(1, 1); // The first 1 is the index
Or, alternatively use Vue.delete(), which is originally to remove properties from object, but can also remove items from arrays:
Vue.delete(this.items, 1); // 1 is the index
More info: https://v2.vuejs.org/v2/api/#Vue-delete

Vue JS Using local variable without defining in data() function

I am a beginner to Vue JS. I have to use a variable inside a component whose value changes often.
So when I declare and define it under data() the following warn is coming in Chrome console
Since when there is a change in data() variables automatically Vue framework calls render function.
Is there any way to declare and use a variable other than declaring it in data() method ??
<template>
<ul>
<div v-for="(list,index) in itemlist" :key="index">
<div v-if="!isFirstCharSame(list.label)" >{{ firstChar }} </div>
<li>
<span>{{ list.label }}</span>
</li>
</div>
</ul>
</template>
<script>
export default {
data() {
return {
itemlist: [
{"label":"Alpha"},
{"label":"Beta"},
{"label":"Charlie"},
{"label":"Delta"}],
firstChar:"$"
}
},
methods : {
isFirstCharSame: function(str) {
if(str.startsWith(this.firstChar)) {
return true;
}
this.firstChar = str.charAt(0);
return false;
}
}
}
</script>
Expected output should be like this
Inside Group A It should display all the elements starting with A
Below we will render using a computed property to make sure its sorted alphabetically and then render your first char. Though You should be using grouping imo.
<template>
<ul>
<div v-for="(list, index) in sortedlist" :key="`people_${index}`">
<div v-if="!isFirstCharSame(list.label)" >{{ firstChar }} </div>
<li>
<span>{{ list.label }}</span>
</li>
</div>
</ul>
</template>
<script>
export default {
data() {
return {
itemlist: [
{"label":"Alpha"},
{"label":"Beta"},
{"label":"Charlie"},
{"label":"Delta"},
],
firstChar: '',
};
},
methods: {
isFirstCharSame(char) {
if (str.startsWith(this.firstChar)) {
return true;
}
this.firstChar = str.charAt(0);
return false;
},
},
computed: {
sortedList() {
return this.itemList.sort((a, b) => {
if (a.label > b.label) {
return 1;
}
if (b.label > a.label) {
return -1;
}
return 0;
});
},
},
};
</script>
And yes, You can update your data any time you wish and the component will do a re render to reflect it.
You can declare variables in your component within your methods or inside computed properties, etc., but they won't be reachable from the template or the rest of the code nor they would be reactive.
The only way for them to be reactive and reachable from the higher scope is adding the data property to the component in the following way:
data: function () {
return {
foo: 'bar'
}
},
or
data () {
return {
foo: 'bar'
}
},
Besides this, the reason of your error is that you are mutating the state of your variables inside the render. When this happens, Vue re-renders the template because the values have mutated and calls again to the function and voilĂ : there you have an infinite loop.
You should probably check the function you are calling and try to replace the changing variables from the data property with local variables that take their data from the actual data variables.

Vue.js: #input for <input> not working with v-for

I am creating my own custom <input> Vue component. What I am doing is that the user can never enter the wrong type of input. For that I am using regex.test() at each input.
This is my code for my Vue component for taking an integer element or an integer array:
<template>
<div>
<label>{{ label }}
<template v-if="isArray">
<input
v-model="arr[i - 1]"
#input="filterInput"
:disabled="disableWhen"
v-for="i in arraySize"
:key="i">
</input>
</template>
<template v-else>
<input
v-model="num"
#input="filterInput"
:disabled="disableWhen">
</input>
</template>
</label>
<el-button
type="success"
icon="el-icon-check"
circle
#click="confirm"
:disabled="disableWhen">
</el-button>
</div>
</template>
<script>
export default {
props: {
label: String,
nonNegative: Boolean,
disableWhen: Boolean,
isArray: Boolean,
arraySize: Number
},
data() {
return {
num: '',
arr: []
}
},
methods: {
filterInput() {
if (this.nonNegative) {
if (!/^[0-9]*$/.test(this.num)) {
this.num = '';
}
} else if (!/^(-)?[0-9]*$/.test(this.num)) {
this.num = '';
}
},
confirm() {
if (this.isArray) {
let validArrayInput = true;
for (let i = 0; i < this.arraySize; i++) {
if (!this.validInput(this.arr[i])) {
validArrayInput = false;
}
}
if (validArrayInput) {
this.$emit('confirm', this.arr);
}
} else if (this.validInput(this.num)) {
this.$emit('confirm', this.num);
}
},
validInput(x) {
return (x !== '' && x !== '-' && typeof x !== "undefined");
}
}
}
</script>
The code is working correctly when isArray = false, that is, for integer elements. But the method filterInput is never being called when isArray = true, and there is no restriction for the wrong input. What is the problem?
filterInput is being called fine for both types of input but it only attempts to manipulate num, it doesn't change arr.
Here's my attempt at implementing this:
const MyInput = {
template: `
<div>
<label>{{ label }}
<template v-if="isArray">
<input
v-for="i in arraySize"
v-model="arr[i - 1]"
:disabled="disableWhen"
:key="i"
#input="filterInput"
>
</template>
<template v-else>
<input
v-model="num"
:disabled="disableWhen"
#input="filterInput"
>
</template>
</label>
</div>
`,
props: {
label: String,
nonNegative: Boolean,
disableWhen: Boolean,
isArray: Boolean,
arraySize: Number
},
data() {
return {
arr: []
}
},
computed: {
num: {
get () {
return this.arr[0]
},
set (num) {
this.arr[0] = num
}
}
},
methods: {
filterInput() {
const arr = this.arr
const re = this.nonNegative ? /^\d*$/ : /^-?\d*$/
for (let index = 0; index < arr.length; ++index) {
if (!re.test(arr[index])) {
this.$set(arr, index, '')
}
}
}
}
}
new Vue({
el: '#app',
components: {
MyInput
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<my-input label="Single"></my-input>
<br>
<my-input label="Multiple" is-array :array-size="3"></my-input>
</div>
A few notes:
I've changed num to be a computed property backed by arr[0]. This simplifies the filtering logic as it only has to consider arr for both types of input. It could be simplified further, e.g. the template doesn't really need to handle two cases, it could treat single-valued just the same as multi-valued but with array-size of 1. Only the value that's emitted (not included in my code) really needs to have different behaviour for the single-valued case. With a little refactoring num could probably be removed altogether.
The implementation is painfully stateful. You're going to run into difficulties if you ever want to pass in values from the outside.
Rather than setting the values to '' I would suggest just stripping out the disallowed characters using replace. I have not made this change in my code, I wanted to retain the behaviour from the original example.
Closing </input> tags are invalid and I have removed them.
There was a lot of duplication in your filterInput method that I've tried to remove. It now checks all the entries in the arr array. There didn't seem to be any need to target the specific input that had changed.
this.$set is used as it's updating an array by index, which otherwise would not be detected by the reactivity system (the standard caveat for manipulating arrays).