Checking for multiple route paths in vue v-bind - javascript

I am currently adding a class to a li element based on the route.path using the following code;
<li v-bind:class="{ 'current': $route.path == '/services' }"><nuxt-link to="/services">Services</nuxt-link>
I want to check for multiple paths but i am unsure if that is possible.
Any help would be great.

It's possible to check for mulitple paths by using an array of possible values, and then Array.prototype.includes to check for existence:
<li v-bind:class="{ 'current': ['/services', '/contact', '/about'].includes($route.path) }">
To improve template readability, consider moving that to a computed prop:
<template>
<li v-bind:class="myClasses">
...
</template>
<script>
export default {
computed: {
myClasses() {
return {
const possiblePaths = ['/services', '/contact', '/about']
current: possiblePaths.includes(this.$route.path)
}
}
}
}
</script>

Related

How to pass an svg in a ternary operator?

I have a very expansive custom input and one of the customizations is autocomplete functionality.
In the interest of not sharing too much, I will just share the bit of the code that is for the suggestion drop down on the autocomplete.
I have a suggestions value and a searchHistory value that is based on the cached search history of the user. To differentiate the two I just want to have a clock or some sort of svg in there. I'm able to render an emoji, but that doesn't fit my design theme so I want to use an clock svg from the library I'm using.
How can I pass that SVG in this ternary operator? I've seen people using '<img :src"../something"/>', but that syntax doesn't seem to work for me either.
Any ideas on how/if I can make this happen?
Cheers!
CAutocompleteList.vue
<template>
<ul>
<li
v-for="suggestion in filteredSuggestions"
:key="suggestion"
#click="$emit('selectSuggestion', suggestion)"
>
{{ suggestion }} {{ searchHistory.includes(suggestion) ? '⏱' : '' }}
</li>
</ul>
<!-- <IconPowerSupplyOne theme="outline" size="100%" fill="#4771FA" /> -->
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
export default defineComponent({
props: {
filteredSuggestions: { type: Array as PropType<string[]>, required: true },
searchHistory: { type: Array as PropType<string[]>, required: true },
},
emits: ['selectSuggestion'],
setup() {
return {}
},
})
</script>
A ternary shouldn't be used here because HTML elements can't be used in text interpolation.
v-if directive should be used instead:
<svg v-if="searchHistory.includes(suggestion)">
...

Vuejs - add different classes in elements generated with v-for

I need to add a class to a list group create using bootstrap 5 in my vuejs app. I know about class binding but in my case I'm not sure how to proceed. I want that when the user click on an item inside the list, the clicked item get the disabled active class and the other elements gets only the disabled class. At the moment I have this code in my template
<ul class="list-group list-group-flush">
<li class="list-group-item list-group-item-action" v-for="(choice, index) in item.choices" :key="index">
<small class="" #click.prevent="checkAnswer(item.questionIndex, index)">{{ index }}) {{ choice }}</small>
</li>
</ul>
The v-for loop will generate the elements and when an element is clicked a method is called to check the user choice. In my app script I have this code
export default {
name: 'Survey',
data() {
return {
n: 0,
answeredQuestions: [],
}
},
mounted() {
},
computed: {
questions() {
return this.$store.getters.survey;
},
},
methods: {
showNext() {
if( this.n < this.questions.length ){
this.n++
}
},
isAnswered(index) {
return this.n !== index ? 'hide' : '';
},
checkAnswer(questionIndex, choice) {
this.answeredQuestions.push(true);
this.showNext();
...
}
}
}
What's the best way to implement the needed class binding?
There's a lot of unknowns about the rest of your code (how the questions are handled and switched through, etc.), but here's a working example for a single question. So you'll have to adapt this for having multiple questions in your app, but it should push you in the right direction. I used an inline :style attribute in addition to the static styles already present on the <li>, but you could move that to a function as suggeted in Peter's answer, if you prefer.
const app = {
name: 'Survey',
data() {
return {
n: 0,
questions: [],
answeredQuestions: [],
item: {
questionIndex: 1,
choices: ['Lorem', 'Ipsum']
},
selectedChoice: null
}
},
mounted() {
},
computed: {
questions() {
return this.$store.getters.survey;
},
},
methods: {
showNext() {
if (this.n < this.questions.length) {
this.n++
}
},
isAnswered(index) {
return this.n !== index ? 'hide' : '';
},
checkAnswer(questionIndex, choice) {
this.answeredQuestions.push(choice);
this.showNext();
}
}
};
Vue.createApp(app).mount('#app');
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
<script src="https://unpkg.com/vue#3.0.11/dist/vue.global.prod.js"></script>
<div id="app">
<ul class="list-group list-group-flush">
<li class="list-group-item list-group-item-action" :class="{disabled: answeredQuestions.length, active: answeredQuestions.includes(index)}" v-for="(choice, index) in item.choices" :key="index" #click.prevent="checkAnswer(item.questionIndex, index)">
<small class="">{{ index }}) {{ choice }}</small>
</li>
</ul>
</div>
If I understand correctly your situation and what you intend to do here I would suggest using the item in the checkAnswer method so that an identifier is used to set a computed property to the current item.questionIndex.
Then you bind the class of each element with a ternary operator condition to check the questionIndex and return the proper classes string: <small :class="questionIndex == item.questionIndex ? 'disabled active':'disabled'" ...
You search the internet for vue class binding and it's the first result that pops up:
https://v2.vuejs.org/v2/guide/class-and-style.html
You can use an plain object, object from your data, a function returning an object or simply a string. You can make any attribute dynamic with v-bind:, or simply :.
Your checkAnswer() function can cause a change in classes by manipulating something in data, for example.
See tutorial above for example code. Keep in mind v-bind:class is the same as :class.
The "best way" changes like every week in Vue, just find a way to do it and learn its advantages and disadvantages.
An example would be:
template: let a function generate the classes
<small
:class="getChoiceClasses(item, choice, index)"
#click.prevent="checkAnswer(item.questionIndex, index)"
>{{ index }}) {{ choice }}</small>
script: add method
getChoiceClasses(item, choice, index) {
let classes = {
active: choice == 1, // for example
disabled: false, // default
even: index % 2 == 0
};
if (whateverYouNeedToCheck) {
classes.disabled = true;
}
return classes;
}
A method is a little slower than a value from data, but it's very minor and only becomes a problem when you have 100s of calls.

Vue JS - Problem with computed property not updating

I am quite new with VueJS and I have been having trouble lately with some computed properties which do not update as I would like. I've done quite some research on Stack Overflow, Vue documentation and other ressources but i haven't found any solution yet.
The "app" is basic. I've got a parent component (Laundry) which has 3 child components (LaundryMachine). The idea is to have for each machine a button which displays its availability and updates the latter when clicked on.
In order to store the availability of all machines, I have a data in the parent component (availabilities) which is an array of booleans. Each element corresponds to a machine's availability.
When I click on the button, I know the array availibities updates correctly thanks to the console.log. However, for each machine, the computed property "available" does not update is I would want it to and I have no clue why.
Here is the code
Parent component:
<div id="machines">
<laundry-machine
name="AA"
v-bind:machineNum="0"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
<laundry-machine
name="BB"
v-bind:machineNum="1"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
<laundry-machine
name="CC"
v-bind:machineNum="2"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
</div>
</div>
</template>
<script>
import LaundryMachine from './LaundryMachine.vue';
export default {
name: 'Laundry',
components: {
'laundry-machine': LaundryMachine
},
data: function() {
return {
availabilities: [true, true, true]
};
},
methods: {
editAvailabilities(index) {
this.availabilities[index] = !this.availabilities[index];
console.log(this.availabilities);
}
}
};
</script>
Child component:
<template>
<div class="about">
<h2>{{ name }}</h2>
<img src="../assets/washing_machine.png" /><br />
<v-btn color="primary" v-on:click="changeAvailability">
{{ this.availability }}</v-btn>
</div>
</template>
<script>
export default {
name: 'LaundryMachine',
props: {
name: String,
machineNum: Number,
availableArray: Array
},
methods: {
changeAvailability: function(event) {
this.$emit('change-avlb', this.machineNum);
console.log(this.availableArray);
console.log('available' + this.available);
}
},
computed: {
available: function() {
return this.availableArray[this.machineNum];
},
availability: function() {
if (this.available) {
return 'disponible';
} else {
return 'indisponible';
}
}
}
};
</script>
Anyway, thanks in advance !
Your problem comes not from the computed properties in the children, rather from the editAvailabilities method in the parent.
The problem is this line in particular:
this.availabilities[index] = !this.availabilities[index];
As you can read here, Vue has problems tracking changes when you modify an array by index.
Instead, you should do:
this.$set(this.availabilities, index, !this.availabilities[index]);
To switch the value at that index and let Vue track that change.

Get data from an Array in Vue

Right now, displayed data in my page is an array as per snippet below:
But I want only to get or display the project names.
This is what I have only right now:
fetch(context,id){
if(context.getters['isEmpty']){
Vue.api.mediasetting.index()
.then(response=>{
var medias = response.data[key];
context.commit('setMedias',medias);
// console.log("init medias", medias[0].project_name)
},response=>{
});
}
},
Can I apply filter here and how? Thanks.
Depends on what html element you want to use for rendering the values, you may use a v-for directive to rendering the project_name values:
<template>
<ul id="example">
<li v-for="(media) in medias">
{{ media.project_name }}
</li>
</ul>
</template>
You are going to need to get the medias data from your store, you may get it using a computed property:
<script>
export default {
computed: {
medias: function () {
return this.$store.medias
}
}
}
</script>
Example above is assuming you are using single file component structure.

How can I add class active when a link clicked and redirect?

My vue component is like this:
<template>
<div>
...
<div class="list-group">
<a :href="baseUrl+'/message/inbox'" class="list-group-item">
Message
</a>
<a :href="baseUrl+'/message/review'" class="list-group-item">
Review
</a>
<a :href="baseUrl+'/message/resolution'" class="list-group-item">
Resolution
</a>
</div>
...
</div>
</template>
<script>
export default {
...
data() {
return {
baseUrl: window.Laravel.baseUrl
}
},
...
}
</script>
When the link is clicked, it will call the url, I want to add class active on the clicked link after reloading the page.
But, I'm still confused, How can I do it?
You could add a computed property, say, currentPath:
computed: {
currentPath () {
// grab current url and return the part after `baseUrl `
// e.g. '/message/inbox' or '/message/review'
return '/message/inbox'
}
}
And a CSS class for your special style:
.active-item {
/* key-value pairs here */
}
Then in your template, you could apply the class to the matched item:
<a :href="baseUrl+'/message/inbox'"
:class="{ 'active-item': currentPath === '/message/inbox'}">
class="list-group-item">Message</a>
<a :href="baseUrl+'/message/review'"
:class="{ 'active-item': currentPath === '/message/review'}">
class="list-group-item">Review</a>
Please read the doc on binding HTML classes with object syntax
Adding to Leo's answer, it's a good idea to introduce a component which will auto detect is active or not, so you don't need to write a bunch of <a> elements and many duplicate attributes.
For example a <custom-link> component:
<custom-link href="/message/inbox" class="list-group-item">
Message
</custom-link>
<custom-link href="/message/review" class="list-group-item">
Review
</custom-link>
<custom-link href="/message/resolution" class="list-group-item">
Resolution
</custom-link>
If you don't need to reuse this component in other components or the list-group-item class is always necessary, you can also encapsulate this class to the <custom-link>. It will look even cleaner:
<custom-link href="/message/inbox">Message</custom-link>
<custom-link href="/message/review">Review</custom-link>
<custom-link href="/message/resolution">Resolution</custom-link>
the code of custom-link looks like:
<a
:href="baseUrl + href"
:class="{ active: isActive }"
class="list-group-item"
>
<slot></slot>
</a>
{
props: ['href'],
data: () => ({ baseUrl: window.Laravel.baseUrl }),
computed: {
isActive () {
return location.href === this.baseUrl + this.href
}
}
}
I use location.href here directly, you can also use a computed property like Leo's example if you need some computations to get the current URL.

Categories

Resources