I have a component which will have many div elements, with only one having an 'active' class on click. I'm trying to bind the 'active' class if a variable 'activeSlide' is equal to the element.
v-bind:class="{ 'active': activeSlide === ?? }"
I'm just not sure what the ?? should be. I haven't been able to find anything that tells how an element can reference itself. Or maybe there is a better approach?
My Code in .vue component file:
<template>
<div v-on:click="show" v-bind:class="{ 'active': activeSlide === ?? }"></div>
</template>
<script>
export default {
data() {
return {
activeSlide: null
}
}
methods: {
show: function(e) {
this.activeSlide = e.target;
}
}
}
</script>
I would assume that the ?? is intended to be something uniquely identifying to the active element.
<template>
<div v-on:click="show('Slide1')" v-bind:class="{ 'active': activeSlide === 'Slide1' }"></div>
<div v-on:click="show('Slide2')" v-bind:class="{ 'active': activeSlide === 'Slide2' }"></div>
</template>
<script>
export default {
data() {
return {
activeSlide: null
}
}
methods: {
show: function(value) {
this.activeSlide = value;
}
}
}
</script>
So basically when you click the slide it will change the activeSlide property to the value passed into the v-on:click method.
There are more dynamic ways of doing this such as if you were to loop through a series of elements you could then compare the active index to the index of the element instead of explicitly saying Slide1 or Slide2.
Here is a dynamic example
<template>
<div v-for="slide in slides" :key="slide.id" v-on:click="show(slide.id)" v-bind:class="{ 'active': activeSlide === slide.id }"></div>
</template>
<script>
export default {
data() {
return {
activeSlide: null
}
}
methods: {
show: function(value) {
this.activeSlide = value;
}
}
}
</script>
so you can use any available data in the iteration to compare, just pass in slide.whatever to the method and set the class to equal the same thing.
Related
I have a sidebar that you can see below:
<template>
<section>
<div class="sidebar">
<router-link v-for="(element, index) in sidebar" :key="index" :to="{ name: routes[index] }" :class='{active : (index==currentIndex) }'>{{ element }}</router-link>
</div>
<div class="sidebar-content">
<div v-if="currentIndex === 0">
Profile
</div>
<div v-if="currentIndex === 1">
Meine Tickets
</div>
</div>
</section>
</template>
<script>
export default {
mounted() {
EventBus.$on(GENERAL_APP_CONSTANTS.Events.CheckAuthentication, () => {
this.authenticated = authHelper.validAuthentication();
});
console.log()
this.checkRouter();
},
data(){
return {
currentIndex:0,
isActive: false,
sidebar: ["Profile", "Meine Tickets"],
routes: ["profile", "my-tickets"],
authenticated: authHelper.validAuthentication(),
}
},
computed: {
getUser() {
return this.$store.state.user;
},
},
methods: {
changeSidebar(index) {
this.object = this.sidebar[index].products;
this.currentIndex=index;
},
checkRouter() {
let router = this.$router.currentRoute.name;
console.log(router);
if(router == 'profile') {
this.currentIndex = 0;
} else if(router == 'my-tickets') {
this.currentIndex = 1;
}
},
},
}
</script>
So when the link is clicked in the sidebar, the route is being changed to 'http://.../my-account/profile' or 'http://.../my-account/my-tickets'. But the problem is currentIndex doesn't change therefore, the content doesn't change and also I cannot add active class into the links. So how do you think I can change the currentIndex, according to the routes. Should I fire an event, could you help me with this also because I dont know how to do it in Vue. I tried to write a function like checkRouter() but it didn't work out. Why do you think it is happening? All solutions will be appreciated.
So if I understand correctly, you want currentIndex to be a value that's based on the current active route? You could create it as a computed property:
currentIndex: function(){
let route = this.$router.currentRoute.name;
if(router == 'profile') {
return 0;
} else if(router == 'my-tickets') {
return 1;
}
}
I think you could leverage Vue's reactivity a lot more than you are doing now, there's no need for multiple copies of the same element, you can just have the properties be reactive.
<div class="sidebar-content">
{{ sidebar[currentIndex] }}
</div>
Also, you might consider having object be a computed property, something like this:
computed: {
getUser() {
return this.$store.state.user;
},
object() {
return this.sidebar[currentIndex].products;
}
},
Just use this.$route inside of any component template. Docs .You can do it simple without your custom logic checkRouter() currentIndex. See simple example:
<div class="sidebar-content">
<div v-if="$route.name === 'profile'">
Profile
</div>
<div v-if="$route.name === 'my-tickets'">
Meine Tickets
</div>
</div>
I have a parent component as a Cart. Here I defined quantity and I want to pass this quantity to the child component's input value which is Counter. So here how I am passing it and here is my parent component, Cart:
<Counter quantity="item.quantity"/>
And here is my child component, Counter:
<template>
<div id="counter">
<button class="minus" #click="countDown"><i :class="quantity == 0 ? 'disabled' : ''" class="fas fa-minus"></i></button>
<div class="count-number"><input class="counter-content" type="number" v-model="quantity"></div>
<button class="plus" #click="countUp"><i class="fas fa-plus"></i></button>
</div>
</template>
<script>
export default {
props: {
quantity: Number
},
methods: {
countUp() {
this.quantity++;
},
countDown() {
if(this.quantity > 0) {
this.quantity--;
}
},
}
}
</script>
I am quite new in Vue, so maybe I am doing something wrong when I pass the props. So could you help me about this?
Try (with the : colon sign)
<Counter :quantity="item.quantity"/>
Before you were just passing the string "item.quanity"
I see you're modifying your prop directly:
countUp() {
this.quantity++;
},
countDown() {
if(this.quantity > 0) {
this.quantity--;
}
},
This is not how you do it in Vue. You need to use two way binding.
countUp() {
this.$emit('input', this.quantity+1)
}
countDown() {
this.$emit('input', this.quantity-1)
}
and in your parent component:
<Counter :quantity="item.quantity" #input="(payload) => {item.quantity = payload}"/>
By the way, the Vue styleguide recommends to use multi-word component names: https://v2.vuejs.org/v2/style-guide/#Multi-word-component-names-essential (Cart = bad, MyCart = good)
We cannot change the value that we get from props, so I created a variable and put props there when mounting
Try it
<Counter :quantity="item.quantity"/>
and
<template>
<div id="counter">
<button class="minus" #click="countDown"><i :class="sum == 0 ? 'disabled' : ''" class="fas fa-minus"></i></button>
<div class="count-number"><input class="counter-content" type="number" v-model="sum"></div>
<button class="plus" #click="countUp"><i class="fas fa-plus"></i></button>
</div>
</template>
<script>
export default {
props: {
quantity: Number
},
data: () => ({
sum: 0
}),
mounted() {
this.sum = this.quantity;
},
methods: {
countUp() {
this.sum++;
},
countDown() {
if(this.sum > 0) {
this.sum--;
}
},
}
}
</script>
When a button is clicked I wish to push it's name to an array. When the button is not clicked I want to remove it's name from an array.
I know how to do this with an #click that pushes/splices the array.
I would like to know if there's a simple way of binding the clicks of the button to the array, just like how a checkbox works with v-model. I understand you cannot use v-model on a button but if we were to make the button it's own component and use v-model on that...
<custom-button v-model="myArray"></custom-button>
Is there a way to make this work?
I would create the structure for the custom-button components like:
...,
props: {
originalArray: {
required: true
}
},
data(){
return {
modifiedArray: this.originalArray.map(x => ({...x}))
}
},
methods: {
yourMethod()
{
//do your logic on the modifiedArray
this.$emit('changed',this.modifiedArray);
}
}
then you could use it like:
<custom-button :original-array="this.myArray" #changed="newArray => this.myArray = newArray" />
I would do it like this:
const CBtn = {
template: '#c-btn',
props: ['array', 'label'],
data(){
return {
ncTimeout: -1
}
},
computed:{
arr_proxy: {
get(){
// shallow copy to not modify parent array indices
return this.array.slice()
}
}
},
methods: {
update(){
this.notClicked()
if(!this.arr_proxy.includes(this.label))
this.$emit('update:array', this.arr_proxy.concat(this.label))
},
notClicked(){
clearTimeout(this.ncTimeout)
this.ncTimeout = setTimeout(()=>{
let index = this.arr_proxy.findIndex(v => v === this.label)
if(index>=0){
this.arr_proxy.splice(index, 1)
this.$emit('update:array', this.arr_proxy)
}
}, 1000)
}
}
}
new Vue({
components: {
CBtn
},
template: '#main',
data(){
return {arr: []}
}
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template id="c-btn">
<button
#click="update"
v-on="$listeners"
v-bind="$attrs"
>
{{label}}
</button>
</template>
<template id="main">
<div>
<c-btn label="1" :array.sync="arr" ></c-btn>
<c-btn label="2" :array.sync="arr" ></c-btn>
<c-btn label="3" :array.sync="arr" ></c-btn>
{{arr}}
<div>
</template>
<div id="app"></div>
So yes you can use v-model with in model option defined value: [propName] and event: [eventName] or the .sync modifier with 'update:[propName]' event.
Consider the following Vue component
<template>
<div class="myclass">
<div class="myotherclass">I am in {{getcurrentClass()}}</div>
</div>
</template>
<script>
export default {
data() {
return {
currentClass: null
}
},
methods: {
getCurrentClass() {
// code to get the class in the context of the place
// it is called in the template
}
}
}
</script>
What I am trying to understand is how to account for the "template context" in a method. In the code above getCurrentClass() would return the class of the element (this is an example, it can be explained to "name of the element", or "id of the element", ...).
Is this at all possible? If so I would appreciate even a general pointer (and I can post the response once I have a solution) - I am not sure which direction I should be looking at to start with.
You can use basic javascript dom referencing techiniques inside the mounted lifecycle hooks.
<template>
<div class="myclass">
<div id="elem" :class="getcurrentClass('myotherclass')">I am in {{ currentClass }}</div>
</div>
</template>
<script>
export default {
data() {
return {
currentClass: null
}
},
methods: {
getCurrentClass(className) {
this.currentClass = className;
return className;
}
},
mounted() {
// here you can get reference to a dom element by using basic javascript dom referencing techniques
const elem = document.querySelector("#elem");
element.classList ....
}
}
</script>
You can also use the Vue $refs object. If used on a child component, it will give you the reference to that child component instance. If used on a plain DOM element, it will give a reference to that element. Read more from here
<template>
<div class="myclass">
<div ref="myElem" :class="getcurrentClass('myotherclass')">I am in {{ currentClass }}</div>
</div>
</template>
<script>
export default {
data() {
return {
currentClass: null
}
},
methods: {
getCurrentClass(className) {
this.currentClass = className;
return className;
}
},
mounted() {
// here you can get reference to a dom element or child component context by using $refs object
const elem = this.$refs.myElem;
myElem.classList ....
}
}
</script>
You could set the currentClass = <className> in state then render it in template:
<template>
<div class="myclass">
<div :class="getcurrentClass('myotherclass')">I am in {{ currentClass }}</div>
</div>
</template>
<script>
export default {
data() {
return {
currentClass: null
}
},
methods: {
getCurrentClass(className) {
this.currentClass = className;
return className;
}
}
}
</script>
What do I have: components structure
<Games> // using v-for - iterate all games
--<Game> // using v-for - iterate all players
----<Player 1>
------<DeleteWithConfirmation>
----<Player 2>
------<DeleteWithConfirmation>
----<Player 3>
------<DeleteWithConfirmation>
----...
<DeleteWithConfirmation> implementation: two clicks are required for deleting game property.
<template>
<div>
<button #click="incrementDelete"
v-html="deleteButtonHTML"></button>
<button v-if="deleteCounter === 1" #click="stopDeleting">
<i class="undo icon"></i>
</button>
</div>
</template>
<script>
export default {
name: 'DeleteWithConfirmation',
data() {
return {
deleteCounter: 0
}
},
computed: {
deleteButtonHTML: function () {
if (this.deleteCounter === 0)
return '<i class="trash icon"></i>'
else
return 'Are you sure?'
}
},
methods: {
incrementDelete() {
this.deleteCounter++
if (this.deleteCounter === 2) {
//tell parent component that deleting is confirmed.
//Parent call AJAX than.
this.$emit('deletingConfirmed')
this.stopDeleting()
}
else if (this.deleteCounter > 2)
this.stopDeleting()
},
stopDeleting() {
Object.assign(this.$data, this.$options.data())
}
}
}
</script>
My problem: seems like indicies are mixed up:
Before deleting 4th player was on "Are you sure state" (deleteCounter === 1), but after deleting it went to initial state (deleteCounter === 0). Seems like 3rd component state haven't updated its deleteCounter, but its data (player's name was updated anyway).
After successfull deleting <Games> component data is fetched again.
You don't need a delete counter for achieving this. On the contrary, it makes it hard to understand your code. Just use a boolean like this:
<template>
<div>
<button #click="clickButton"
<template v-if="confirmation">
<i class="trash icon"></i>
</template>
<template v-else>
Are you sure?
</template>
</button>
<button v-if="confirmation" #click="confirmation = false">
<i class="undo icon"></i>
</button>
</div>
</template>
<script>
export default {
name: 'DeleteWithConfirmation',
data() {
return {
confirmation: false
}
},
methods: {
clickButton() {
if (!this.confirmation) {
this.confirmation = true;
} else {
this.$emit('deleting-confirmed');
}
}
}
</script>
The parent could then be looking e.g. like this:
<div class="row" v-if="showButton">
[...]
<delete-with-confirmation #deleting-confirmed="showButton = false">
</div>
One of the answers was deleted, I wish I could mention the initial author, but I don't remeber his username, so (changed a bit):
incrementDelete() {
if (this.deleteCounter === 1) { // 1 because there is "post-increment" at the end of the fucntion
this.deletingProgress = true
this.$emit('deletingConfirmed')
this.stopDeleting()
}
else if (this.deleteCounter > 1) // 1 because there is "post-increment" at the end of the fucntion
this.stopDeleting()
this.deleteCounter++ // "post-increment"
},
ssc-hrep3 answer is more clean (and lightweight) than mine approach, link.