How to concat text class with Vuejs 2 - vuejs2

How do I dynamically concat a text with vuejs 2
Here is what I have now:
<span class="label" :class="label-{{account.Segment}}">{{account.Segment}}</span>
account.Segment == "ABC"
What I need rendered is that
<span class="label label-ABC">ABC</span>

Here is one possible way.
<span class="label" :class="'label-' + account.Segment">{{account.Segment}}</span>

You can add more classes using an array of string
computed: {
classNames() {
// add more logic here
let classNames = ['label'];
classNames.push(`label-${this.contextType}`);
return classNames;
},
},
and then you can use it like this
<div :class="classNames">
...
</div>

Related

Vue does not correctly remove item from vfor

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

How can I convert conditional VueJS if statement to a Computed Property?

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.

Get all selected values on change with VueJS 3

I have these lines inside my component:
<li
v-for="(c, i) in choice[messagesRobot[numMessage].choice]"
:key="`choice-${choice[messagesRobot[numMessage].choice]}-${i}`">
<input
#change="changeValue($event, messagesRobot[numMessage].choice)"
:value="c.id"
type="checkbox"
:name="choix[messagesRobot[numMessage].choice]"
:id="`c-${choix[messagesRobot[numMessage].choice]}-${i}`">
<label :for="`c-${choix[messagesRobot[numMessage].choice]}-${i}`">
{{ c.nom }}
</label>
</li>
As you can see, there are a lot of computed properties. What I would like is to update the model when the user changes the value. The problem is that I can't use something like :v-model="xxx", so I have to capture the #change event. I wrote this:
function changeValue(e, v) {
console.log(v);
console.log(JSON.stringify(e.target.value));
}
The second line only gives me the last element the user selected or unselected, what I would like is to get ALL the values of the checkboxes, in an array, as it would do if I could use a v-model.
Thanks in advance for your help :)
I think you could do something like this
<li
v-for="(c, i) in choice[messagesRobot[numMessage].choice]"
:key="`choice-${choice[messagesRobot[numMessage].choice]}-${i}`">
<input
#change="changeValue($event, messagesRobot[numMessage].choice, i)"
:value="c.id"
type="checkbox"
:name="choix[messagesRobot[numMessage].choice]"
:id="`c-${choix[messagesRobot[numMessage].choice]}-${i}`">
<label :for="`c-${choix[messagesRobot[numMessage].choice]}-${i}`">
{{ c.nom }}
</label>
</li>
data() {
return {
valuesArray: []
}
}
function changeValue(e, v, index) {
this.valuesArray[index] = e.target.value;
console.log(v);
console.log(JSON.stringify(e.target.value));
}
then your valuesArray would have all the checked values.
Why can't you just use v-model?

Dynamic computed property in html

I'm using php for creating set of computed properties in my app based on ID property of each object in my main array of data stored in property deals. So i have now computed properties like list_10_deals_cnt, list_20_deals_cnt, list_30_deals_cnt etc. Now, how can I create these dynamic created properties in span with class dials__cnt while looping my array of data? {{'list_'+el.id+'_deals_cnt'}} is not working as i wish, its display just a string like list_10_deals_cnt instead to display a computed value.
P.S. sorry about my english.
<div class="dials" id="app">
<div class="dials__column" v-for="(el, index) in deals">
<div class="dials__header">
<div>{{el.title}}</div>
<div>сделок: <span class="dials__cnt">{{`'list_'+el.id+'_deals_cnt'`}}</span>, <span></span> руб</div>
</div>
<draggable drag-class="draggable-ghost__class" class="dials__block" :list="el.items" group="deal" #change="log(el.id, $event)">
<div
class="dials__card"
v-for="(el, index) in el.items"
:key="el.title"
>
<div class="card__header">
<div class="card__title">{{ el.customer_name }}, {{ el.customer_company_name }}</div>
<div class="card__date">{{ el.created_at }}</div>
</div>
<div class="card__body">
<a :href="'/deal/detail/'+el.id" class="card__link">{{ el.title }}</a>
</div>
<div class="card__footer">
<span class="card__price">{{ el.price }} руб </span>
<span v-for="(tag, index) in el.tags" class="card__tag">{{ tag }}</span>
</div>
</div>
</draggable>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
deals: <?php echo json_encode($deals, JSON_UNESCAPED_UNICODE); ?>
},
computed: {
<?php
foreach ($deals as $k=>$v) {
echo 'list_'.$v->id.'_deals_cnt: function() {return this.deals['.$k.'].items.length},';
}
?>
},
methods: {
log: function(id, evt) {
if (evt.hasOwnProperty('added')) {
let dealID = evt.added.element.id;
console.log('сделка ID '+dealID+' перемещена в статус '+id+'. Отправляем аякс запрос на обновление статуса в бд');
// ajax for update
}
}
}
});
</script>
Hi
Problem 1
you will not be able to get the value of computed property by using this
{{`'list_'+el.id+'_deals_cnt'`}}
for the same reason as console.log('vari' + 'able') doesn't print out value of variable to the console.
( Vue evaluates whatever is in between {{ }} as an expression ).
Solution
I suppose, you can either use the deals property directly in html as shown below without using a computed property
<div class="dials__column" v-for="(el, index) in deals">
<div class="dials__header">
<div>{{el.title}}</div>
<div>сделок: <span class="dials__cnt">{{ el.items.length }}</span>, <span></span> руб</div>
</div>
----------- rest of the code
or you can create a computed property based on deals data property and use that to loop in html using v-for.
Problem 2
I don't think below code is valid php string. although it becomes irrelevant if you use first solution above.
<?php
foreach ($deals as $k=>$v) {
echo 'list_'.$v->id.'_deals_cnt: function() {return this.deals['.$k.'].items.length},';
}
?>
the ' inside ['.$k.'] should be escaped.
Solution
echo 'list_'.$v->id.'_deals_cnt: function() {return this.deals[\'.$k.\'].items.length},';

Set focus to first item in v-for using vue

I have a vue app and I'm trying to set the focus to the first item in my v-for list but struggling
HTML
<div class="input-group-append">
<button class="btn btn-light" type="submit" style="border: 1px solid lightgray" #click.prevent="findTest">
<i class="fas fa-search"></i>
</button>
</div>
<div v-if="this.Tests.length >= 2" class="list-group accList">
<a v-for="(test, i) in tests" :key="i" class="list-group-item list-group-item-action" :ref="i" :class="{ 'active': i === 0 }" #click.prevent="selectTest(test)">
{{ test.test1 }} ({{ test.test2 | capitalize }})
</a>
</div>
Note the word 'test' has replaced my actual values
I have tried using the following in my method which is called on button click but I keep getting get an error
methods: {
findTest() {
axios.get(END_POINT).then((response) => {
...SOMEOTHER CODE
//this.$refs.0.$el.focus()
//this.$refs.0.focus();
//this.$refs.a.$el.children[0].focus();
}
}
}
Error
I am relativly new to vue but I have been able to set my focus using:
this.$refs.[INPUT_NAME].$el.focus()
But it doesn't like me using a number
this.$refs.0.$el.focus() //0 being the index number
As WebStorm complains saying:
Expecting newline or semicolon
console.log(this.$refs)
When using v-for, ref maybe array of refs.
Template: use ref="tests" instead of :ref="i"
<a v-for="(test, i) in tests" :key="i" class="list-group-item list-group-item-action" ref="tests" :class="{ 'active': i === 0 }" #click.prevent="selectTest(test)">
{{ test.test1 }} ({{ test.test2 | capitalize }})
</a>
Script
this.$refs.tests[0]
I guess its undefined because you try to access the element while the v-for loop didnt finished its rendering
Try this:
methods: {
findTest() {
axios.get(END_POINT).then((response) => {
...SOMEOTHER CODE
this.$nextTick(()=> {
//put your code here
//this.$refs.0.$el.focus()
//this.$refs.0.focus();
//this.$refs.a.$el.children[0].focus();
})
}
}
}
Put your code into $nextTick() that should ensure that its get executed when the loop is done