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

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">

Related

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.

How can I access text inside an input within a template?

My objective is to get text from an input that's in a template. Not sure how to go about retrieving this. I'm using Vue; Note must be available in Vue.js, no external sources
The Template:
<template id="addmodal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
Enter Course Information:
</slot>
</div>
<div class="modal-body">
<slot name="body">
Course Name
<input type="text" ref="coursename" placeholder="Numbers Don't Lie 101">
Course Grade
<input type="text" ref ="coursemark" placeholder="100">
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button class="modal-default-button" #click="confirmCourse">
Submit New Course
</button>
<button class="modal-cancel-button" #click="cancelCourse">
Cancel
</button>
</slot>
</div>
</div>
</div>
</div>
</template>
I need to access coursename and coursemark. This can be done fairly easily when not inside a template. As it is right now the code executes stating .value is undefined.
var app = new Vue({
el: "#app",
data: {
courses: [],
confirmModal: false,
confirmAdd: false,
selectedCourse: null
},
methods: {
addCourse2: function addCourse2() {
this.confirmAdd = false;
var course = this.$refs.coursename.value;
var mark = this.$refs.coursemark.value;
if (course) {
this.courses.push(new Course(course, mark));
this.$refs.newcourse.value = "";
this.$refs.newmark.value = "";
}
}
}
});
EDIT:
Forgot to add the component section
Vue.component("add-modal", {
template: "#addmodal",
props: ["open", "course", "mark"],
methods: {
confirmCourse: function confirmCourse() {
alert(this.$refs.coursename.value);
this.$emit("confirm");// GET
},
cancelCourse: function cancelCourse() {
this.$emit("cancel");
}
}
});
Forgive me in advance, I feel this is something rather easy I'm missing as a beginner
use v-model. or if it is in other component. you can use $emit

vuejs render part of template inside different elements without repeating

I am new to Vuejs. This is what I need to do.
<div v-for="r in records">
<div v-if="r.something">
<div id="x">
{{ r. something}}
more of r here.
</div>
</div>
<div v-else id="x">
same div as in the block above.
</div>
</div>
What I want do is not define div with id x two times as it is huge.
Make your 'div' a component and refer to it in both places.
There are many ways to define your component. This is example shows just one. If you are using WebPack, use a single file component. You can then have your script, html, and css all in one file that gets precompiled. That's the best way to manage your 'huge' div. Then you can continue to refactor and break it up into more components.
const myComponent = {
template: "<div :id='id'>HELLO, my id is {{id}}. r.foo is {{r.foo}} </div>",
props: {
id: String
},
data() {
return {
r: {
foo: 'bar'
}
}
}
}
<div v-for="r in records">
<div v-if="r.something">
<my-component id='x' />
</div>
<div v-else id="x">
<my-component id='x' />
</div>
</div>

Conditional a href in Vuejs

I am rendering a list of store titles in VueJS, some of them have a url property, some of them don't. If the title has a url, I want to add a a href property:
<div v-for="(store, index) in stores">
<span v-if="store.link"><a :href="store.link" target="_blank">{{ store.title }}</a></span>
<span v-else="store.link">{{ store.title }}</span>
</div>
This works, but the code looks duplicated. Is there anyway to simplify the code further?
you can use component tag:
var app = new Vue({
el: '#app',
data () {
return {
stores: [
{title:'product1',link:'/products/222'},
{title:'product2'},
{title:'product3',link:'/products/333'},
{title:'product4'}
]
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<div v-for="(store, index) in stores">
<component :is="store.link?'a':'span'" :href="store.link || ''" target="_blank">{{store.title}}
</component>
</div>
</div>
I'd remove the first span element, as it's not necessary. Also, the v-else does not need the conditional statement (it's not v-else-if):
new Vue({
el: '#app',
data: {
stores: [
{ link: 'foo', title: 'foo-text' },
{ title: 'bar-text' }
]
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div v-for="(store, index) in stores" :key="index">
<a v-if="store.link" :href="store.link" target="_blank">{{ store.title }}</a>
<span v-else>{{ store.title }}</span>
</div>
</div>
You can use dynamic arguments in vue3
https://v3.vuejs.org/guide/template-syntax.html#dynamic-arguments
<a v-bind:[attributeName]="url"> ... </a>
or binding an object of attributes
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>

VueJS: How to output a comma separated array?

I know that in VueJS I can loop through an array:
<span v-for="(element, index) in list">{{ element }}</span>
But what if I wanted a list that is comma separated? For example, if list = ["alice", "bob", "chuck"], then the above would output:
<span>alice</span><span>bob</span><span>chuck</span>
What I want, though, is:
<span>alice</span>, <span>bob</span>, <span>chuck</span>
Is this possible?
If all you care about is comma separation, use Javascript's built-in join method:
{{ list.join(', ') }}
For arrays of objects, you could do something like this:
{{ list.map(entry => entry.title).join(', ') }}
You can use conditional rendering to hide last , like following:
var demo = new Vue({
el: '#demo',
data: function() {
return {
lists: ['Vue', 'Angular', 'React']
};
}
})
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id="demo">
<span v-for="(list, index) in lists">
<span>{{list}}</span><span v-if="index+1 < lists.length">, </span>
</span>
</div>
You could do it using a v-if attribute with a condition over the first argument, avoiding the usage of .length:
var app = new Vue({
el: '#app',
data: {
list: ['john', 'fred', 'harry']
}
})
<script src="https://vuejs.org/js/vue.min.js"></script><div id="app">
<span v-for="(element, index) in list">
<span v-if="index != 0">, </span><span>{{ element }}</span>
</span>
</div>
What I ended up doing instead was:
<span v-for="element in list" class="item">
<span>{{ element }}</span>
</span>
And in CSS:
.item + .item:before {
content: ", ";
}
Solution using "template"
<template v-for="(element, index) in list">
<span>{{element}}</span><template v-if="index + 1 < list.length">, </template>
</template>
If you wanted to do it the JS way, you can just do a computed property; you could even continue the span method.
computed {
listCommaSeparated () { return this.list.join(', '); },
listCommaSpans () { return '<span>' + this.list.join('</span><span>') + '</span>'; },
},
This would definitely be the preferred way from a rendering performance standpoint.
Just adding another alternative which I prefer to use:
<span v-for="(item, index) in list">
{{ item }}{{ (index+1 < list.length) ? ', ' : '' }}
</span>
My component:
<template>
<ul v-if="model.length">
<li v-for="item in model">{{item}}</li>
</ul>
</template>
<style scoped>
ul {
list-style: none;
}
li {
display: inline-block;
}
li:not(:last-child)::after {
content: ", ";
}
</style>
<script>
export default {
props: ['model']
};
</script>
Its possible sample
<span v-for="(item,i) in items">
{{(item !='' && i !=0) ? ',' : ''}} {{item.title}}
</span>
If you do want control over how the dom looks (e.g. actually want to achieve the dom structure that you asked about), you can create a functional component like so:
<script>
// RenderList.vue
export default {
functional: true,
render(createElement, context) {
// Read the list of entries by accessing the prop "list"
const listEntries = context.props.list;
// Return a custom list of elements for each list entry.
// Only return a `,` if it's not the last entry.
return listEntries.map((listElement, idx) => {
return [
createElement("span", listElement),
idx < listEntries.length - 1 ? ", " : "",
];
});
},
};
</script>
You would use this component like so:
<template>
<RenderList :list="['alice', 'bob', 'chuck']" />
</template>
May I suggest using i > 0 as check?
I've wrapped the separator so that {{ ' ' }} can be used for just having a space and avoiding span treated as being empty.
<span
v-for="(item, i) in list"
:key="i">
<span v-if="i>0">{{ ', ' }}</span>
<span class="text-nowrap">{{ item }}</span>
</span>
or
<span
v-for="(item, i) in list"
:key="i">
<!-- if not wrapped in a span will leave a space before the comma -->
<span>{{ (i > 0 ? ', ' : '') }}</span>
<span class="text-nowrap">{{ item }}</span>
</span>
There are different views where using :after or :before to have a comma is not a good idea with respect to browser not handling it as a string when the window is resized.
If we try to use conditional operator in each iteration to check whether it is the first or the last element, it would be an overhead if we have large number of items. Also the conditions will be re-evaluated whenever we have change detection.
To overcome these 2 things, we can enclose the comma in a separate element and hide it using CSS :last-child selector as
<template v-for="item in list">
<span class="item">
{{item}}
<span class="comma">
,
</span>
</span>
</template>
and in CSS
.item:last-child .comma {
display: none;
}