I have this custom component in vue callled "dm-vehicle-spec"
<dm-vehicle-spec #_handleRemoveSpec="_handleRemoveSpec" v-for="spec, index in vehicleSpecs" :key="index" :index="index" :spec="spec"></dm-vehicle-spec>
which looks like the following
<script>
export default {
props: ["spec"],
data() {
return {
specName: null,
specValue: null,
}
},
mounted() {
if (this.spec.detail_name && this.spec.detail_value) {
this.specName = this.spec.detail_name;
this.specValue = this.spec.detail_value;
}
},
computed: {
getSpecNameInputName() {
return `spec_${this.spec.id}_name`;
},
getSpecValueInputName() {
return `spec_${this.spec.id}_value`;
},
},
methods: {
_handleRemoveSpec() {
this.$emit("_handleRemoveSpec", this.spec.id);
}
},
}
</script>
<template>
<div class="specs-row flex gap-2 w-full items-center">
<div class="col-1 w-5/12">
<input placeholder="Naam" type="text" :id="getSpecNameInputName" class="w-full h-12 spec_name rounded-lg border-2 border-primary pl-2" v-model="specName">
</div>
<div class="col-2 w-5/12">
<input placeholder="Waarde" type="text" :id="getSpecValueInputName" class="w-full h-12 spec_name rounded-lg border-2 border-primary pl-2" v-model="specValue">
</div>
<div #click="_handleRemoveSpec" class="col-3 w-2/12 flex items-center justify-center">
<i class="fas fa-trash text-lg"></i>
</div>
</div>
</template>
so when i have 3 specs, 1 from the database and 2 customs i have the following array vehicleSpecs (Which i loop over)
[
{"id":23,"vehicle_id":"1","detail_name":"Type","detail_value":"Snel","created_at":"2022-11-07T19:06:26.000000Z","updated_at":"2022-11-07T19:06:26.000000Z","deleted_at":null},
{"id":24},
{"id":25}
]
so lets say i want to remove the second item from the list so the one with test1 as values, then the array looks like
[{"id":23,"vehicle_id":"1","detail_name":"Type","detail_value":"Snel","created_at":"2022-11-07T19:06:26.000000Z","updated_at":"2022-11-07T19:06:26.000000Z","deleted_at":null},{"id":25}]
So the second array item is removed and thats correct because object with id 24 no longer exsist but my html shows
that the value for object with id 24 still exists but the value for object with id 25 is removed, how is that possible?
If u need any more code or explaination, let me know
Any help or suggestions are welcome!
Index is not a good choice to use as v-for key.
Indexes are changing when you delete something from array.
Try using another property as a key.
Run the loop in reverse order. This way, deleted items do not effect the remaining item indexes
Hit: indexReverse = list.length - index
Related
How can I convert the code below to a computed property in Vue?
What I am trying to achieve is:
I would like to display the div tag if the type that has been chosen is not userKeys or adminKeys. However, if the type is determined to be userKeys or adminKeys, then I want the userKeysIcon and adminKeysIcon to be shown respectively. Is there a way I can achieve this using a computed value in VueJs?
var markUp = Vue.compile('\
<a class="box" ref="addProofBox" href="#" role="button" :id="\'Add_\' + type" #click.prevent="clickBox" v-preventTabbing:[preventTabbingValue]>\
<div class="ms-Grid">\
<div class="ms-Grid-row">\
<div class="ms-Grid-col" :class="{ \'ms-sm2\': this.type != \'showMoreOptions\' }">\
<div v-if="type != \'userKeys\'" class="box-icon" aria-hidden="true" :class="icon"></div>\
<img v-else-if="type = userKeys" :src="userKeysIcon" role="presentation" class="box-icon-img"/>\
<img v-else ="type = adminKeys" :src="adminKeysIcon" role="presentation" class="box-icon-img"/>\
</div>\
<div class="ms-Grid-col ms-sm10">\
<div class="box-title">{{ title }}</div>\
<div class="box-description" :class="{ \'text-align-center\': this.type == \'showMoreOptions\' }">{{ desc }}</div>\
</div>\
</div>\
</div>\
</a>');
As long as type is not dynamically provided in the template itself, e.g. as result of a loop, just create a computed property and use it.
I'm not sure why you use the Vue.compile approach, so here is a example in the current composition api pattern and assuming that type is a component prop:
<template>
<a class="box" ref="addProofBox" href="#" role="button" :id="'Add_' + type" #click.prevent="clickBox" v-preventTabbing:[preventTabbingValue]>
<div class="ms-Grid">
<div class="ms-Grid-row">
<div class="ms-Grid-col" :class="{ 'ms-sm2': type != 'showMoreOptions' }">
<img v-if="hasMatchingKeys" :src="keysIcon" role="presentation" class="box-icon-img" />
<div v-else class="box-icon" aria-hidden="true" :class="icon" />
</div>
...
</div>
</div>
</a>
export default {
props: {
type: {
type: String,
required: true
},
userKeysIcon: {
type: String,
required: true
},
adminKeysIcon: {
type: String,
required: true
},
},
setup(props) {
const hasMatchingKeys = computed(()=>["userKeys", "adminKeys"].includes(props.type));
const keysIcon = computed(()=>{
const {type, adminKeysIcon, userKeysIcon} = props;
const iconMap = new Map([['userKeys', userKeysIcon],['adminKeys', adminKeysIcon]])
const icon = iconMap.get(type)
return icon || ""
});
return {isUserKeys, keysIcon}
}
}
i would also suggest to move all string concatenations and and object-buildings (like the one where class names are determined) into computed props. not only it is better to read, but also the vue code styles recommend to reduce complexity from templates.
I am working on a Vue Assignment. For styling I am using BootstapVue. What I need to achieve is, whenever an user is entering a text in the input field, a filtered array that is containing the value should be displayed as dropdown. The User can either press Enter key stroke or select the value from dropdown. If the input text is not in the array, then the user should not be able to enter the value i.e. the tag might not be created. For example, whenever we are creating a question here in Stack Overflow, upon typing tags and entering or selecting from the suggestion cards. I I need similar functionality except that if the input text is not in the suggestion array, the user will not be able to create the tag
Here's what I have tried so far :
App.vue
<template>
<div>
<b-form-tags
v-model="value"
#input="resetInputValue()"
tag-variant="success"
:state="state"
>
<template v-slot="{ tags, inputId, placeholder, addTag, removeTag }">
<b-input-group>
<b-form-input
v-model="newTag"
list="my-list-id"
:id="inputId"
:placeholder="placeholder"
:formatter="formatter"
#keypress.enter="addTag(newTag)"
></b-form-input>
</b-input-group>
<b-form-invalid-feedback :state="state">
Duplicate tag value cannot be added again!
</b-form-invalid-feedback>
<ul v-if="tags.length > 0" class="mb-0">
<li
v-for="tag in tags"
:key="tag"
:title="`Tag: ${tag}`"
class="mt-2"
>
<span class="d-flex align-items-center">
<span class="mr-2">{{ tag }}</span>
<b-button
size="sm"
variant="outline-danger"
#click="removeTag(tag)"
>
remove tag
</b-button>
</span>
</li>
</ul>
<b-form-text v-else>
There are no tags specified. Add a new tag above.
</b-form-text>
</template>
</b-form-tags>
<datalist id="my-list-id">
<option>Manual Option</option>
<option v-for="(size, ind) in sizes" :key="ind">{{ size }}</option>
</datalist>
</div>
</template>
<script>
export default {
data() {
return {
newTag: "",
value: [],
sizes: ["Small", "Medium", "Large", "Extra Large"],
};
},
computed: {
state() {
// Return false (invalid) if new tag is a duplicate
return this.value.indexOf(this.newTag.trim()) > -1 ? false : null;
},
},
methods: {
resetInputValue() {
this.newTag = "";
},
formatter(value) {
if (this.sizes.includes(value)) {
return value.toUpperCase();
}
return ;
},
},
};
</script>
How can I achieve the same? Thanks
You can achieve it by using b-form-datalist with the use of <b-form-input> by adding a change event to reset the input value if there is no data in the dataList as per the search value.
Demo :
new Vue({
el: '#app',
data: {
sizes: ['Small', 'Medium', 'Large', 'Extra Large'],
tag: null
},
methods: {
checkTag(e) {
const tagsAvailable = this.sizes.filter((item) => item === e);
if (!tagsAvailable.length) {
this.tag = ''
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.21.2/bootstrap-vue.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.21.2/bootstrap-vue.min.css"/>
<div id="app">
<b-form-input list="my-list-id" v-model="tag" v-on:change="checkTag($event)"></b-form-input>
<datalist id="my-list-id">
<option>Manual Option</option>
<option v-for="size in sizes">{{ size }}</option>
</datalist>
</div>
I'm running into the problem, that Vue converts the value of an input field of type number into a string and I just can't figure out why. The guide I am following along does not run into this issue and get's the values as numbers, as expected.
The vue docs state, that Vue would convert the value to a number, if the type of the input is number.
The code is originated from a component, but I adjusted it to run in JSFiddle: https://jsfiddle.net/d5wLsnvp/3/
<template>
<div class="col-sm-6 col-md-4">
<div class="panel panel-success">
<div class="panel-heading">
<h3 class="panel-title">
{{ stock.name }}
<small>(Price: {{ stock.price }})</small>
</h3>
</div>
<div class="panel-body">
<div class="pull-left">
<input type="number" class="form-control" placeholder="Quantity" v-model="quantity"/>
</div>
<div class="pull-right">
<button class="btn btn-success" #click="buyStock">Buy</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['stock'],
data() {
return {
quantity: 0 // Init with 0 stays a number
};
},
methods: {
buyStock() {
const order = {
stockId: this.stock.id,
stockPrice: this.stock.price,
quantity: this.quantity
};
console.log(order);
this.quantity = 0; // Reset to 0 is a number
}
}
}
</script>
The quantity value is the issue.
It is initialized with 0 and when I just press the "Buy" button, the console shows:
Object { stockId: 1, stockPrice: 110, quantity: 0 }
But as soon as I change the value by either using the spinners or just type in a new value, the console will show:
Object { stockId: 1, stockPrice: 110, quantity: "1" }
Tested with Firefox 59.0.2 and Chrome 65.0.3325.181. Both state that they are up to date. I actually also tried it in Microsoft Edge, with the same result.
So what am I missing here? Why is Vue not converting the value to a number?
You need to use .number modifier for v-model, like this:
<input v-model.number="quantity" type="number">
Note: empty string ('') is not converted to a number, so you may need to handle it separately.
Change the order object to :
const order = {
stockId: this.stock.id,
stockPrice: this.stock.price,
quantity: +this.quantity
};
This will automatically parse the string to a number.
In general the data from HTML inputs are strings. The input type only checks if a valid string has been provided in the field.
I'm asking if we have similar attribute as standalone in vuejs.
I want to add items in certifications.
Here is the button add :
<l-button #click="Add()"><i class="fa fa-plus"> </i></l-button>
The add function:
Add() {
this.certifications.push( item);
this.newItemAdded = true
},
<div v-for="(item,index) in certifications">
<fg-input type="text" required
:label="$t('candidate.certification.title')"
:placeholder="$t('candidate.certification.title')"
:disabled="disableIt && !newItemAdded ">
</fg-input>
</div>
My problem is that when i add new item, the previous input also is enabled.
I want to know if there is somathing similar to standalone( like angular) in vuejs.
You must use the index and length of certifications
<div v-for="(item,index) in certifications">
<fg-input type="text" required
:label="$t('candidate.certification.title')"
:placeholder="$t('candidate.certification.title')"
:disabled="(index+1!==certifications.length)">
</fg-input>
</div>
and when you need edit button:
data() {
return {
certifications: [],
editableIndex: null
}
},
methods: {
Add() {
...
},
Edit(index) {
this.editableIndex = index
},
}
}
and in template
<template>
<div>
<div v-for="(item,index) in certifications">
<fg-input type="text" required
:label="$t('candidate.certification.title')"
:placeholder="$t('candidate.certification.title')"
:disabled="(index+1!==certifications.length&&editableIndex===null) || editableIndex===index">
</fg-input>
<l-button #click="Edit(index)"><i class="fa fa-pen"> </i></l-button>
</div>
<l-button #click="Add()"><i class="fa fa-plus"> </i></l-button>
</div>
</template>
сеI have an array with categories. Each category has children.
When a parent is checked/unchecked, its children must be checked/unchecked as well. If all children are checked, the parent must be checked as well.
Vue updates the fields as expected, but doesn't re-render. I cannot understand why.
Here is my code:
<template>
<div class="card">
<div class="card-body">
<ul class="list-tree">
<li v-for="category in categories" :key="category.id" v-show="category.show">
<div class="custom-control custom-checkbox">
<input :id="'category-' + category.id"
v-model="category.checked"
#change="checkParent(category)"
type="checkbox"
class="custom-control-input" />
<label class="custom-control-label"
:for="'category-' + category.id">
{{ category.name }}
</label>
</div>
<ul>
<li v-for="child in category.children" :key="child.id" v-show="child.show">
<div class="custom-control custom-checkbox">
<input :id="'category-' + child.id"
v-model="child.checked"
#change="checkChild(child, category)"
type="checkbox"
class="custom-control-input" />
<label class="custom-control-label" :for="'category-' + child.id">
{{ child.name }}
<small class="counter">({{ child.products_count }})</small>
</label>
</div>
</li>
</ul>
</li>
</ul>
</div>
</div>
</template>
export default {
data () {
return {
categories: [],
listItemTemplate: { show: true, markedText: null, checked: false }
}
},
methods: {
checkParent (category) {
category.children.forEach(child => {
child.checked = category.checked
})
},
initializeCategories () {
this.categories = []
this.originalCategories.forEach(originalCategory => {
var parent = this.copyObject(originalCategory)
this.categories.push(parent)
parent.children.forEach (child => {
child = this.copyObject(child)
})
})
},
copyObject (category) {
return Object.assign(category, {...this.listItemTemplate})
}
},
computed: {
...mapState({
originalCategories: state => state.categories,
})
},
mounted () {
this.initializeCategories()
}
}
You need to expand your scope, since you are changing it only within checkParent() method, variables that you are making changes to will not have an effect onto components variables.
Use the index instead of value in categories iteration to find correct category, and then apply changes in scope of whole component:
<li v-for="(category, categoryIndex) in categories" :key="category.id" v-show="category.show">
<div class="custom-control custom-checkbox">
<input :id="'category-' + category.id"
v-model="category.checked"
#change="checkParent(categoryIndex)"
type="checkbox"
class="custom-control-input" />
<label class="custom-control-label"
:for="'category-' + category.id">
{{ category.name }}
</label>
</div> <!-- the rest of the code ... -->
And then in component's method:
methods: {
checkParent (categoryIndex) {
let categoryChecked = this.categories[categoryIndex];
this.categories[categoryIndex].children.forEach((child, childIndex) => {
this.categories[categoryIndex].children[childIndex].checked = categoryChecked;
})
},
I fixed it. The problem wasn't related to the checkboxes at all. The problem was related to the way I've created the categories array.
When I initialize the component, I copy the array from vuex and add new properties (like checked) in order to check the children when the parent is checked. I didn't follow the rules for adding new fields, that's why the children wasn't reactive and didn't get checked when the parent was checked.
Thanks a lot for your effort to help me!