Apply styling to certain items in a list in Vue - vue.js

how should I go about applying an attribute to a class for styling only for certain elements in a list? It will be clearer in the example below:
<u-col
v-for="(item, i) in items"
:key="i"
:class="classNames.viewport"
>
data () {
return {
classNames: {
viewport: "col-lg-3 col-md-3 col-sm-3 col-6 d-flex justify-content-center"
}
}
}
Assuming i only goes from 0-3. Currently every item in v-for has the class viewport for styling. What I am hoping to achieve is to only apply an additional margin style mb-3 if i is greater than 1.
ie, the viewport becomes "col-lg-3 col-md-3 col-sm-3 col-6 d-flex justify-content-center mb-3" for items with i > 1, while viewport does not contain mb-3 for items with i < 1.
I thought about using a v-for and v-if together but I believe it is not a good practice to do so, https://www.codecheef.org/article/dont-use-v-if-with-v-for-elements.

In :class bind, you can check i and add mb-3 to class when i > 1:
<u-col
v-for="(item, i) in items"
:key="i"
:class="classNames.viewport + (i > 1 ? ' mb-3' : '')"
>

Try like this
:class="`${classNames.viewport} ${i > 1 ? 'mb-3' : ''}`"

You can use Vue's Class and Style bindings and pass it an object.
<u-col
v-for="(item, i) in items"
:key="i"
:class="{
'col-lg-3 col-md-3 col-sm-3 col-6 d-flex justify-content-center': true,
'mb-3': i > 1,
}"
/>

Related

Can I create a conditional v-for loop to loop on the same div?

I'm curious is there is a way to have a v-for loop with a conditional statement inside of it so I can reduce redundancies in my program. I have a div tag that needs to loop on the tagfitlers object if the tag_filters object does not exist, otherwise, I need it to loop on the tag_fitlers object.
This is a snippet of my current loop:
<div v-else class="text-left mt-2 border" v-for="(filter, index) in tagfilters" :key="index">
<span v-for="(f, i) in filter" :key="i">
<div class="d-flex justify-content-between align-items-center pr-3 pl-3 pt-3">
<!-- Multiselect Search with Tagging -->
<div>
<multiselect #change="onEdit(filter, 'code', f.code)" class="mb-2" v-model="f.code" placeholder="Search & Select Code Options" :custom-label="customCodesLabel" track-by="code" :options="codesArr"></multiselect>
</div>
</div>
</div>
I am hoping to do something like this:
v-for="tag_filters ? (filter, index) in tag_filters : (filter, index) in tagfilters"
Is this possible?
According to Vue.js,
Using v-if and v-for together is not recommended. See the style guide for further information.
https://v2.vuejs.org/v2/guide/conditional.html#v-if-with-v-for
You could use a computed property to get the correct list.
new Vue({
el: "#app",
data: function() {
return {
tag_filters: {
filterC: "tag_filter C.",
filterD: "tag_filter D.",
},
tagfilters: {
filterA: "tagfilter A.",
filterB: "tagfilter B."
}
}
},
computed: {
getTagFilters() {
if (Object.keys(this.tag_filters).length === 0) {
return this.tagfilters;
} else {
return this.tag_filters;
}
}
}
})
<div id="app">
<div v-for="(filter, key) of getTagFilters" :key="key">
<span>{{ filter }}</span>
</div>
<button #click="tag_filters = {}">Remove tag_filter data</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
One option is to use a computed prop, but not sure if it is enough for what you need:
get realTagFilters() {
return this.tagFilters ?? this.tag_filters;
}
And you can call it like
<div v-for="(filter, index) in realTagFilters">
Or you can try with the logical or operator, which also seems to work:
<div v-for="filter in (tag_filters || tagFilters)" :key="filter.id">

VUEjs how to make a banner appear every 3 elements

Good afternoon
Help me how to make a banner appear every 3-4 elements.
Here's a suggestion:
// elements is [1,2]
<div v-for="(e, i) in elements" :key="i">
<div></div>
<div></div>
<div></div>
<Banner></Banner>
<div>
Or you could do some conditional rendering based on the index so:
// elements is [1,2,3,4,5,6,7,8]
<div v-for="(e, i) in elements" :key="i">
<Banner v-if="!((i + 1) % 4)" />
<div v-else></div>
<div>

Vue - passing v-for index from parent to child component

I've done the research but can't find a good answer. My parent and child component code is below. How do I pass the index for the v-for loop in the parent to the child component for use there? I want to hide any of the gauges past #4 for mobile screens, but show all of them on a desktop.
Parent:
<div class="col-md-8 col-xs-6">
<div class="row flex-nowrap">
<data-block
v-for="(gauge, index) in device.gauges"
:metric="gauge.metric"
:value="gauge.value"
:unit="gauge.unit"
:alarm="gauge.alarm"
:idx="index">
</data-block>
</div>
</div>
Child:
app.component('data-block', {
props: ['alarm', 'metric','value','unit','idx'],
template: `<div v-bind:class="'col card px-0 text-center border' + ((alarm) ? ' border-danger':' border-success') + ((idx > 3) ? ' d-none d-md-block':'')">\
<div class="card-header p-1">{{metric}}</div>\
<div class="card-body p-1 align-middle">\
<h1 class=" text-center display-1 font-weight-normal text-nowrap">{{value}}</h1>\
</div>\
<div class="card-footer p-1">{{unit}}</div>\
</div>`,
created: ()=> console.log(this.idx) //yields "undefined"
})
You're passing the idx prop correctly, but Instead of checking its value inside created hook, try displaying it in the template instead, to make sure it's not an issue with timing (it might not be defined when the child component is created):
<div>{{idx}}</div>
Also, to make the code easier to read and write, I would suggest you to move the static classes to class attribute and the dynamic classes to v-bind:class attribute, and also make it multiline:
template: `
<div
class="col card px-0 text-center border"
:class="{
'd-none d-md-block': idx > 3,
'border-danger': alarm,
'border-success': !alarm
}"
>
...
`

Is it possible to concatenate class properties in vuejs?

General Question: I was wondering if I can concatenate class properties based on condition. See pseudo-code in the v-forline.
My use-case: I want to align all images that have an even index at the right side.
If I use flex flex-row-reverse for the parent section I get the images aligned on the right. But I don't know how to construct the class in such a way that I do not have to repeat the code for the child elements.
<section
v-for="(quote, index) of $frontmatter.quotes :class="lg:flex my-4 mx-12 overflow-auto" + {even: index % 2, odd: !(index % 2)}"
>
<img
class="quote-image right rounded-full h-64 w-64 flex items-center justify-center shadow-md mx-auto lg:ml-16 lg:mr-10 mt-12"
:src="$withBase(quote.image)"
/>
<div class="px-8 pt-6">
<h3 class="text-primary font-bold mb-4">
{{ quote.title }}
</h3>
<p>
{{ quote.text }}
</p>
</div>
</section>
And call the class extension something like:
.even {
flex-row-reverse
}
Currently,I use this structure - however, I am not happy with that, as I have to repeat my code for the child elements...
<section
v-for="(quote, index) of $frontmatter.quotes"
class= "my-16 mx-24 overflow-auto"
>
<div v-if="index % 2"
class="lg:flex flex-row-reverse">
<img
class="quote-image rounded-full h-64 w-64 flex items-center justify-center shadow-md mx-auto lg:ml-16 lg:mr-10 mt-12"
:src="$withBase(quote.image)"
/>
<div class="px-8 pt-6">
<blockquote>
<h2 class="text-primary font-bold mb-4">
{{ quote.title }}
</h2>
</blockquote>
<p class="quote-text">
{{ quote.text }}
</p>
</div>
</div>
<div v-else
class="lg:flex">
<img
class="quote-image rounded-full h-64 w-64 flex items-center justify-center shadow-md mx-auto lg:ml-16 lg:mr-10 mt-12"
:src="$withBase(quote.image)"
/>
<div class="px-8 pt-6">
<blockquote>
<h2 class="text-primary font-bold mb-4">
{{ quote.title }}
</h2>
</blockquote>
<p class="quote-text">
{{ quote.text }}
</p>
</div>
</div>
</section>
It should look something like:
First, just to clarify the question I'm trying to answer. Given the following code:
<div v-if="index % 2" class="lg:flex flex-row-reverse">
... children ...
</div>
<div v-else class="lg:flex">
... identical children ...
</div>
Is there a way to avoid the v-if and conditionally add the flex-row-reverse class instead?
You've got a number of options here. Firstly, the attributes class and style have special behaviour that allows you to specify both a bound and static copy of the same attribute. You can't do that for other attributes. e.g.
<div
class="lg:flex"
:class="{ 'flex-row-reverse': index % 2 }"
>
So the class lg:flex is added as a static class whereas, flex-row-reverse is added conditionally. Vue will combine them as appropriate to create the class attribute of the finished DOM nodes.
There are a number of other ways this could be written. Here are a couple to ponder:
<div :class="{ 'lg:flex': true, 'flex-row-reverse': index % 2 }">
<div :class="['lg:flex', { 'flex-row-reverse': index % 2 }]">
Arrays can be nested arbitrarily deep. Plain strings will be treated as classes to add. Objects will be treated as collections of conditional classes with the class name as the property name and the condition the property value.
All of this is using Vue's support for conditionally adding classes. However, a bound expression is just arbitrary JavaScript so you could apply the conditionality using normal JavaScript syntax instead of having Vue do it for you.
This is generally clunkier. This has to be a single expression so you can't use if/else. However, you can use ?: instead:
<div :class="'lg:flex' + (index % 2 ? ' flex-row-reverse' : '')">
The official documentation for these various ways to build classes is here:
https://v2.vuejs.org/v2/guide/class-and-style.html#Binding-HTML-Classes
Since you are using classes, not <b-img> props, perhaps the class you need is float-right instead of right
<img v-if="index % 2"
class="quote-image float-right rounded-full h-64 w-64 shadow-md mx-auto lg:ml-16 lg:mr-10 mt-12"
:src="$withBase(quote.image)"
/>

Show Div When Hover Based on ID in Vue JS

I have a div that when i want hover to it, it will show other div. However, my first div is dynamic, it has an ID. So how will i able to hover to ID based on its ID?
It should be #mouseenter="hoverService{{services.id}} = true" but it causes error. So i made the code below to just static.
Here's my code below:
<template>
<div
class="col-md-3"
v-for="(services, index) in servicesFiltered"
:key="index"
#mouseenter="hoverService = true"
#mouseleave="hoverService = false"
>
<div class="service_background" v-if="hoverService">
<div class="mb-1" v-for="(sub_services, index) in services.menu_items" :key="index">
<router-link
:to="{ path: `/${sub_services.data}`}"
>
<a
href="#"
class="btn btn-outline-primary w-100 services_button"
>{{sub_services.text }}</a>
</router-link>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
hoverService: false
};
}
};
</script>
Try this code
https://codesandbox.io/s/y7p9qyyovz
You need to maintain hover for each item you can not manipulate using single variable for multiple items.