Ionic Vue Checkboxes - vue.js

I have the following Problem:
I'm using Ionic Vue and a VueX Store to save my data from an API.
Now I have set an array, which contains the IDs of entries, which shall be checked or unchecked.
Since I should not modify the API-Model class, I have saved the IDs of checked entries in a seperate Array in my VueX Store, which I update as needed.
Now I'm trying to make the checkboxes checked / unchecked depending on that array.
I tried it by adding v-model = "checkedVehicles.included(vehicle.vehicle_id)", but all I get is an Error:
'v-model' directives require the attribute value which is valid as LHS vue/valid-v-model
Heres the Part whit the checkboxes, hope that is all you need :)
<IonItem v-for="vehicle in vehicleList" v-bind:key="vehicle.vehicle_id">
<IonLabel>
<h2>{{ vehicle.manufacturer }} {{ vehicle.model }}</h2>
<p>{{ vehicle.display_name }}</p></IonLabel>
<IonCheckbox slot="end"
v-model="checkedVehicles.includes(vehicle.vehicle_id)"
#click="checkIfAllDeselected"
#update="this.updateCheckboxOnClick(vehicle.vehicle_id)"/>
</IonItem>
The checkedVehicles Arrays is intialized as String[].
Also tried to use a function, which returns true or false, depending on the checkedVehicles Array has the ID included or not, but that also gives the same error
The other functions, which add or remove entires to the correspondig arrays are working fine, already checked that. only the Checkboxes are not working as intended.
Has anyone a clue, what I'm doing wrong?

This is obvious because we can't evaluate a condition in v-model. We generally bind a variable to the v-model.
For eg:
Consider you have a attr called vehicle in data.
data() {
return {
Vehicle list: [{
checked: true,
manufacturer: '',
display_name: '',
vehicle_id: ''
},
{
checked: true,
manufacturer: '',
display_name: '',
vehicle_id: ''
},
]
}
then you can bind it as
<IonItem v-for="vehicle in vehicleList" v-bind:key="vehicle.vehicle_id">
<IonLabel>
<h2>{{ vehicle.manufacturer }} {{ vehicle.model }}</h2>
<p>{{ vehicle.display_name }}</p></IonLabel>
<IonCheckbox slot="end"
v-model="vehicle.checked"
#click="checkIfAllDeselected"
#update="this.updateCheckboxOnClick(vehicle.vehicle_id)"/>
</IonItem>
To conclude, variables that can hold value can only be used in v-model

Related

Any problems with defining variables inside v-for?

AFAIK, first shown here by Vladimir Milosevic
<div
v-for="( id, index, user=usersData[id], doubleId=doubleThat(id) ) in users"
:key="id">
{{ user.name }}, {{ user.age }} years old And DoubleId = {{ doubleId }}
</div>
I expanded his codepen. Looks like you can define several variables local to the loop this way.
Are there any side-effects or risks you see with this technique?
I find this way to be the most convenient and clean out there but since that is apparently not the intended use it may not be future-proof.
EDIT:
Here is another pen which demonstrates the use case better. While here we reference just one computed property in one place, if the number of properties and references grows, it will lead to a verbose code.
In general, I think the clean way to do what you want to do is to bring the data into a format, where you can loop over it without extra steps. This way, you will never need those additional variables in v-for.
The example from your codepen could look like this:
const app = new Vue({
el: '#app',
data: {
years: [2024, 2025, 2120],
usersData: {...}
},
computed: {
// user objects for each year
ageData() {
return Object.fromEntries(this.years.map(year => [year,
Object.values(this.usersData).map(user => this.userInYear(user, year))
] ))
}
},
methods: {
userInYear(user, year){
return {...user, age: user.age + (year - 2023)}
}
},
});
Now you can iterate it without interruption and shenanigans:
<div v-for="(users, year) in ageData" :key="year">
<b>In {{ year }}:</b>
<div v-for="user in users" :key="user.name">
{{ user.name }} will be {{ user.age }} years old.
</div>
</div>
It is easier to read and change, and it has the added benefit that you can inspect ageData in the console.
That said, I think it is very interesting that you can declare additional variables in v-for, I assume it is possible because of the way vue parses and builds expressions from the provided string, I am pretty sure it won't be removed, and if you and the people you work with are comfortable with it (and are prepared to hear the above over and over like just now), by all means, go for it.

Vuejs 3 alternative select binding

I'm trying to get a select element bound to a value for a custom object. The crux here is that the object property in question has a custom getter. The value is set as a number, but when accessed returns an associated value as a string. Why I do this is a long story.
So I have an object of key-value pairs making some options:
<select v-model="myObject.myProperty">
<option v-for="v, k in myOptions" :key="k" :value="k">{{v}}</option>
</select>
{{myObject.myProperty}} //this line prints out the correct value
But the options are not showing as selected. The value is updated for myObject.myProperty and it returns what I expect. I suspect that behind the scenes, it's correctly assigning k to my custom object, but that because it returns a different string value, Vue can't inherently figure out which option to mark 'selected'.
Manually adding :selected does not help:
<option v-for="v, k in myOptions" :key="k" :value="k" :selected="v === myObject.myProperty">{{v}}</option>
I also tried to manually bind the select instead of using the v-model attribute, also no:
<select :value="myObject.myProperty" #input="myObject.myProperty = $event.target.value"
Is there an alternative way to wire up a select/option situation? If not, building a custom component with faux-select functionality is my next step.
For clarity, myOptions is a key-value like this
{
0 : 'Option 1',
1 : 'Option 2',
}
But myObject has special setters that take and remember the key, then also a special getter than returns the value from myOptions.
So then:
myObject.myProperty = 0;
console.log(myObject.myProperty) //logs 'Option 1'
When when I set the value to the key (k) I get back the corresponding value when the option is selected and the value of 'myObject.myProperty' is what I expect. Example: I pick 'Option 1' from the drop-down, which has a value of 0 derived from the key k.
However, although myObject.myProperty has the value I want, I can't get Vue to display the the actual html option as selected, probably because the value returned by myObject.myProperty is 'Option 1' and not 0
Alright, the actual answer:
<select #input="myObject.myProperty = $event.target.value">
<option v-for="v, k in myOptions" :selected="myObject.myProperty === v" :key="k" :value="k">{{v}}</option>
</select>
v-model won't work here because it simply doesn't care that you've manually applied selected: it will always try to match the option value to the the v-model property value. As this object takes one value with a setter and returns another with a getter, these will never align.
Instead, manually assign the value to the object with #input and match the value from the getter to the value in the options for selected.
Not sure I understand your question properly or not. Hence, adding my input on your requirement below.
As myObject.myProperty returning the value you passed in the select and as per your code you are passing the index as value.
Hence, while comparing in :selected both LHS and RHS should contain index of the item you passed.
Working Demo :
const app = new Vue({
el: '#app',
data() {
return {
myOptions: [{
id: 1,
name: 'alpha'
}, {
id: 2,
name: 'beta'
}, {
id: 3,
name: 'gama'
}],
myObject: {
myProperty: ''
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<select v-model="myObject.myProperty">
<option v-for="(item, index) in myOptions" :selected="myObject.myProperty === item.id" :key="item.id" :value="item.id">{{item.name}}</option>
</select>
{{myObject.myProperty}}
</div>
UPDATE Based on the newly provided information.
In Template:
<select v-model="selectedOption">
<option v-for="(v, k) in options" :key="k" :value="k">
{{ v }}
</option>
</select>
In JS
data() {
return {
selectedOption: null,
options: {
0: 'Option 1',
1: 'Option 2',
},
};
}
By setting selectedOption with the key of options will correctly display the selected option.
Here is a link to a working example using your data structure. It contains two selects both using the same object for its data source. One with a default selection. the other without.
https://stackblitz.com/edit/vue-efekym?file=src/App.vue
THIS IS NOW OUTDATED BASED ON THE LATEST INFO
Based on the logic in your further examples I think the problem is within conflating your k & v variables in your loop. Although it is hard to tell because there isn't any sample data or a complete isolated component outlying the behavior.
However in your later examples you have:
<option v-for="v, k in myOptions" :key="k" :value="k" :selected="v === myObject.myProperty">{{v}}</option>
I am inferring that you believe that myObject.myProperty is holding the value from variable v, when in fact you are setting the value as variable k as witnessed in :value="k"
By correcting this I believe your issue will be resolved, you also noted a int to string conversion. depending on how/where this is happening this could also contribute to your headaches because this will never equate to true. '1231' === 1231

Can I pass metadata through Vue Formulate's schema API without affecting input attributes?

The goal:
generate form fields from JSON/CMS
have a param in the JSON that allows two fields to sit next to each other on a single line
The solution so far:
I’m using Vue Formulate's schema API to generate fields. In Vue Formulate's options, I can conditionally add a class to the outer container based on a parameter in the context.
classes: {
outer(context, classes) {
if (context.attrs.colspan === 1) {
return classes.concat('col-span-1')
}
return classes.concat('col-span-2')
},
I’m using Tailwind, which requires no classname concatenation and actually want the default to be col-span-2, so if you’re inclined to copy this, your logic may vary.
With a few classes applied to the FormulateForm, this works really well. No additional wrapper rows required thanks to CSS grid:
<FormulateForm
v-model="values"
class="sm:grid sm:grid-cols-2 sm:gap-2"
:schema="schema"
/>
The schema now looks something like this:
[
{
type: 'text',
name: 'first_name',
label: 'First name',
validation: 'required',
required: true,
colspan: 1,
},
The problem/question
Vue Formulate’s schema API passes all attributes defined (other than some reserved names) down to the input element. In my case, that results in:
<div
data-classification="text"
data-type="text"
class="formulate-input col-span-1"
data-has-errors="true"
>
<div class="formulate-input-wrapper">
<label
for="formulate-global-1"
class="formulate-input-label formulate-input-label--before"
>
First name
</label>
<div
data-type="text"
class="formulate-input-element formulate-input-element--text"
>
<input
type="text"
required="required"
colspan="1" <--------------- hmm…
id="formulate-global-1"
name="first_name"
>
</div>
</div>
</div>
I recognize that I can name my attribute data-colspan so that I’m not placing a td attribute on an input, but I think of colspan as metadata that I don’t want applied to the template. Is there a way to prevent this from being applied to the input—perhaps a reserved word in the schema API that allows an object of metadata to be accessed via context without getting applied to v-bind="$attrs"?
The vue-formulate team helped me out on this one. Very grateful. Much love.
There is a way to prevent it from landing on the input, and that's to use the reserved outer-class property in the schema:
[
{
type: 'text',
name: 'first_name',
label: 'First name',
validation: 'required',
required: true,
'outer-class': ['col-span-1'],
},
This means that I don't need to do this at all:
classes: {
outer(context, classes) {
if (context.attrs.colspan === 1) {
return classes.concat('col-span-1')
}
return classes.concat('col-span-2')
},
vue-formulate supports replacing or concatenating classes via props. I managed to overlook it because I didn't recognize that everything you pass into the schema API is ultimately the same as applying a prop of that name.
Classes can be applied to several other parts of the component as well—not just the outer/container. More information here:
https://vueformulate.com/guide/theming/customizing-classes/#changing-classes-with-props

Using counter flag in v-for loop

I want to use a counter flag in v-for inside another v-for loop for counting total run of inside loop.
Here is my template:
<a :href="'#/product/'+list.id" :id="ikeyCounter" v-for="item,ikey in section.list" class="movie item fcosuable">
{{ ikeyCounterPlus() }}
<div class="verticalImage">
<div class="loader hideloading"></div>
<img :src="item.thumb" alt="">
</div>
</a>
data() {
return {
loading: true,
status: null,
list: [],
sections: null,
ikeyCounter: 3
}
},
And method:
ikeyCounterPlus() {
this.ikeyCounter++;
},
but I'm getting wrong result on ikeyCounter variable. Id of a tag started from "15003" to "15150", if I don't call ikeyCounterPlus() inside v-for tag, for loop will run correctly (150 run)
If you want to count your objects, then just count your data. No need to involve DOM.
section.list is an array, so section.list.length should give you desired count.
Also, as mentioned in the answer before, use some unique property of item (for example some sort of id) as the value for :key attribute.
You can't do it like this, Vue.js is reactive framework and you should learn a little bit before asking these kind of questions - https://v2.vuejs.org/v2/guide/reactivity.html
Use your key as id instead

Vuejs2 - How to update the whole object from the server not losing reactivity?

I have a list of objects that can be updated from the database.
So, when I load the list, objects have only id and name.
When I click on an object I load other fields that can be of any length - that's why I don't load them with the objects in the list.
I found that when I update an object it can be difficult to keep reactivity https://v2.vuejs.org/v2/guide/reactivity.html so I need to find some workaround.
this code works almost okay:
axios.get('/api/news', item.id).then(function(response){
if (response){
Object.assign(item, response.data.item);
}
});
But the problem is the fields that have not been presented from the beginning is not 100% reactive anymore. What is going on is a new field has been updated reactively only when I change another, previous one. So, if I show 2 text field with old and new properties, if I change the second property, the field will not be updated until I change the first one.
I got item object from the component:
data () {
return {
items: [],
}
},
and
<div v-for="item in items" #click="selectItem(item)" >
<span>{{item.name}}</span>
</div>
Then item's been passed to the function selectItem.
What is the proper pattern to load new fields and keep them reactive? (NB: it's not the case when I can assign field by field - I want to reuse the same code no matter which object it is, so I need so the solution for updating an object at a time without listing all new fields.)
Note. This code works inside the component.
Completely revised post: Ok, the example you give uses an array, which has its own caveats, in particular that you can't directly set values like vm.items[indexOfItem] = newValue and have it react.
So you have to use Vue.set with the array as the first argument and the index as the second. Here's an example that adds a name property to object items and then uses Vue.set to set the item to a new object created by Object.assign.
new Vue({
el: '#app',
data: {
items: [{
id: 1,
other: 'data'
}, {
id: 2,
other: 'thingy'
}]
},
methods: {
selectItem(parent, key) {
const newObj = Object.assign({}, parent[key], {
name: 'some new name'
});
Vue.set(parent, key, newObj);
setTimeout(() => {parent[key].name = 'Look, reactive!';}, 1500);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="item, index in items" #click="selectItem(items, index)">
<span>{{item.name || 'empty'}}</span>
</div>
<pre>
{{JSON.stringify(items, null, 2)}}
</pre>
</div>
Have a look at Change-Detection-Caveats Vue cannot detect property addition or deletion if you use "normal assign" methods.
You must use Vue.set(object, key, value)
Try something like the following:
axios.get('/api/news', item.id).then(function(response){
if (response){
let item = {}
Vue.set(item, 'data', response.data.item)
}
});
Than item.data would than be reactiv.
Can simply use Vue.set to update this.item reactively
axios.get('/api/news', item.id).then(function(response){
if (response){
this.$set(this, "item", response.data.item);
}
});