How to update canvas on "v-if" switching in Vue? - javascript

Div with class progress-wrapper drawning on a canvas. How can I change this code to re-draw canwas after each switching of list? Atm it draws just once.
template
<button #click="switcher = !switcher">SWITCH</button>
<transition name="fade">
<li v-for="elements in myData" v-if="elements.key == getKey()">
<span>{{elements.title}}</span>
<div class="progress-wrapper" :data-value="elements.progress/100"></div>
</li>
</transition>
script
data() {
return {
switcher: true,
}
getKey(){
if (this.switcher) {
return 'KEY'
} else {
return 'ANOTHER KEY'
}
style
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave {
opacity: 0;
}

Ok, you can try this. I've put some comments to highlight parts that need some tweaking.
<template>
<div>
<button #click="switcher = !switcher">SWITCH</button>
<transition name="fade">
<!-- is a good practice keep v-if and v-for in separate elements -->
<div v-if="elements.key === currentKey">
<!-- also you need to add key property when using a for loop -->
<li
v-for="elements in myData"
:key="elements.key">
<span>{{ elements.title }}</span>
<div
:data-value="elements.progress/100"
class="progress-wrapper"></div>
</li>
</div>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
switcher: true
};
},
computed: {
// this will return the new value of currentKey as soon as the value of switcher changes
currentKey() {
return this.switcher ? "KEY" : "ANOTHER KEY";
}
},
};
</script>
Here's more info on combining v-for and v-if in the same node:
https://v2.vuejs.org/v2/guide/list.html#v-for-with-v-if

Related

Whenever i click next or prev on my image slider it scrolls to the top of page how do i stop this?

<script>
export default {
name: "Slider",
data() {
return {
images: [
"https://cdn.pixabay.com/photo/2015/12/12/15/24/amsterdam-1089646_1280.jpg",
"https://cdn.pixabay.com/photo/2016/02/17/23/03/usa-1206240_1280.jpg",
"../assets/sample-1.jpg"
"https://cdn.pixabay.com/photo/2016/12/04/19/30/berlin-cathedral-1882397_1280.jpg"
],
currentIndex: 0
};
},
methods: {
next: function() {
this.currentIndex += 1;
},
prev: function() {
this.currentIndex -= 1;
}
},
computed: {
currentImg: function() {
return this.images[Math.abs(this.currentIndex) % this.images.length];
}
}
};
</script>
vue.js below
<template>
<div>
<transition-group name="fade" tag="div">
<div v-for="i in [currentIndex]" :key="i">
<img :src="currentImg" />
</div>
</transition-group>
<a class="prev" #click="prev" href="#">❮ Previous</a>
<a class="next" #click="next" href="#">❯ Next</a>
</div>
Just scrolls to the top every time i click on either prev or next can't figure out why.
Also i havent been able to get any of my own images from assets to appear in the slider and not sure why it isnt able to retrieve them this way (as in the sample-1.jpg) Thanks.
Remove href="#"
You dont need v-for or transition-group. A simple transition with mode="out-in" is enough. You put the key to the current image, so vue always knows if the image gets replaced so it can do a transition
Dont put images into the assets folder. You better put it into the static folder.
Then you can access your image like /sample-1.jpg
new Vue({
name: "Slider",
el: "#app",
data() {
return {
images: [
"https://cdn.pixabay.com/photo/2015/12/12/15/24/amsterdam-1089646_1280.jpg",
"https://cdn.pixabay.com/photo/2016/02/17/23/03/usa-1206240_1280.jpg",
"/sample-1.jpg",
"https://cdn.pixabay.com/photo/2016/12/04/19/30/berlin-cathedral-1882397_1280.jpg"
],
currentIndex: 0
};
},
})
.v-enter-active,
.v-leave-active {
transition: opacity 1s ease;
}
.v-enter,
.v-leave-to {
opacity: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<a class="prev" #click="currentIndex--" v-if="currentIndex > 0">❮ Previous</a>
<a class="next" #click="currentIndex++" v-if="currentIndex < images.length - 1">❯ Next</a>
<transition name="v" mode="out-in">
<img :key="images[currentIndex]" :src="images[currentIndex]" />
</transition>
</div>

Vue.js slots - how to retrieve slot content in computed properties

I have a problem with vue.js slots. On one hand I need to display the slot code. On the other hand I need to use it in a textarea to send it to external source.
main.vue
<template>
<div class="main">
<my-code>
<template v-slot:css-code>
#custom-css {
width: 300px
height: 200px;
}
</template>
<template v-slot:html-code>
<ul id="custom-css">
<li> aaa </li>
<li> bbb </li>
<li> ccc </li>
</ul>
</template>
</my-code>
</div>
</template>
my-code.vue
<template>
<div class="my-code">
<!-- display the code -->
<component :is="'style'" :name="codeId"><slot name="css-code"></slot></component>
<slot name="html-code"></slot>
<!-- send the code -->
<form method="post" action="https://my-external-service.com/">
<textarea name="html">{{theHTML}}</textarea>
<textarea name="css">{{theCSS}}</textarea>
<input type="submit">
</form>
</div>
</template>
<script>
export default {
name: 'myCode',
props: {
codeId: String,
},
computed: {
theHTML() {
return this.$slots['html-code']; /* The problem is here, it returns vNodes. */
},
theCSS() {
return this.$slots['css-code'][0].text;
},
}
}
</script>
The issues is that vue doesn't turn the slot content. It's an array of <VNode> elements. Is there a way to use slots inside the textarea. Or a way to retrieve slot content in the theHTML() computed property.
NOTE: I use this component in vuePress.
You need to create a custom component or a custom function to render VNode to html directly. I think that will be the simplest solution.
vnode to html.vue
<script>
export default {
props: ["vnode"],
render(createElement) {
return createElement("template", [this.vnode]);
},
mounted() {
this.$emit(
"html",
[...this.$el.childNodes].map((n) => n.outerHTML).join("\n")
);
},
};
</script>
Then you can use it to your component
template>
<div class="my-code">
<!-- display the code -->
<component :is="'style'" :name="codeId"
><slot name="css-code"></slot
></component>
<slot name="html-code"></slot>
<!-- send the code -->
<Vnode :vnode="theHTML" #html="html = $event" />
<form method="post" action="https://my-external-service.com/">
<textarea name="html" v-model="html"></textarea>
<textarea name="css" v-model="theCSS"></textarea>
<input type="submit" />
</form>
</div>
</template>
<script>
import Vnode from "./vnode-to-html";
export default {
name: "myCode",
components: {
Vnode,
},
props: {
codeId: String,
},
data() {
return {
html: "", // add this property to get the plain HTML
};
},
computed: {
theHTML() {
return this.$slots[
"html-code"
]
},
theCSS() {
return this.$slots["css-code"][0].text;
},
},
};
</script>
this thread might help How to pass html template as props to Vue component

Vue active class v-for

I have a question about v-for. Why do I have to return this.activeClass = {...this.activeClass} to update the component? Why component didn't update after this line.
if (this.activeClass[index]) {
this.activeClass[index] = false;
} else {
this.activeClass[index] = true;
}
I want set background to red on click on v-for element
Template:
<template>
<div class="container">
<div class="row mt-5">
<div
v-for="(quote, i) in quotes"
:key="i"
#click="del(i)"
:class="{red: activeClass[i]}"
class="quote col-3"
>
{{ quote }}
</div>
</div>
</div>
</template>
Script:
<script>
export default {
props: ["quotes"],
data: function() {
return {
activeClass: {}
};
},
methods: {
del(index) {
if (this.activeClass[index]) {
this.activeClass[index] = false;
} else {
this.activeClass[index] = true;
}
this.activeClass = {...this.activeClass};
}
}
};
</script>
Vuejs is not reactive on deep object. look at https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
In addition to what the others have said, I can't help but feel that you're over-complicating this slightly. You're already looping over an object (quotes), why not apply an active property to those instead?
HTML
<div
v-for="(quote, i) in quotes"
:key="i"
#click="del(quote)"
:class="{active: quote.active}"
class="quote col-3"
>
{{ quote }}
</div>
Method Change
del(quote) {
quote.active = !quote.active;
}
New CSS
.quote.active{
background-color: red;
}

VueJS Select only one element in a v-for

I'm using Vue v2
I'm trying to change only the properties of the selected element. See, when the response is marked after the click, it should change to a red color with a text that says 'Unmark'. And vice versa: if the button is clicked again (which now would say 'Unmark'), it should change to a green color and the text would be 'Mark'. Alas, it works.... Nevertheless, my code applies the change to every element inside the v-for; I only want that to happen to the selected element.
I've thought about using a Component to check if somethings changes, but first I'd like to see if there's a solutions for this. ANy help will be appreciated
Here's my code:
<div class="search-results">
<div class="activity-box-w" v-for="user in users">
<div class="box">
<div class="avatar" :style="{ 'background-image': 'url(' + user.customer.profile_picture + ')' }">
</div>
<div class="info">
<div class="role">
#{{ '#' + user.username }}
</div>
<div>
<div>
<p class="title">#{{ user.customer.name }}
#{{user.customer.lastname}}
</p>
</div>
</div>
</div>
<div class="time">
<input type="button" class="btn btn-sm btn-primary" v-on:click.prevent="markUser(user)" v-model="text"
v-bind:class="[{'green-border':notMarked}, {'red-border':marked}]" v-cloak v-if="!action"
:disabled="action"></input>
</div>
</div>
</div>
Now for the script:
new Vue({
el: '#engage-panel',
data: {
users: [],
mark: {'id' : '', 'marks' : ''},
text: 'Mark', //Migth change to Unmark later on
action: false,
marked: null,
notMarked: null,
},
methods:
{
markUser: function(user){
this.mark.id = user.id;
this.action= true;
Vue.http.headers.common['X-CSRF-TOKEN'] = $('meta[name="csrf-token"]').attr('content');
this.$http.put('/mark/user', this.mark).then(response => {
if(response.body === "marked")
{
this.mark.marks="true";
this.text = 'Unmark';
this.marked= true;
this.notMarked= false;
this.action= false;
}else{
this.mark.marks="false";
this.text = 'Mark';
this.marked= false;
this.notMarked= true;
this.action= false;
}
}).catch(e => {
this.action= false;
});
}
}
You can use $event.target on click if you just need to toggle css class.
fiddle here
But it's true that it's easier if a user has a status like marked = true/false for example, you just need to bind class like :
<input :class="{ 'green-border': user.marked, 'red-border': !user.marked }">
The issue my code applies the change to every element you met is caused by every user in v-for="user in users" uses one same object to indicates it is marked or not.
If your users data has one property like status to save current status (like unmark, mark etc), it is very simple, just change to next status when click mark button.
If your users data doesn't have that property, you need to create one dictionary, then save the users already clicked as key, the status for the user will be the value.
Below is one demo:
app = new Vue({
el: "#app",
data: {
users1: [{'name':'abc', 'status':'none'},
{'name':'xyz', 'status':'none'}],
users2: [{'name':'abc'}, {'name':'xyz'}],
selectedUsers: {}
},
methods: {
getNextStatusBaseOnRoute: function (status) {
if(status ==='marked') return 'marked'
let routes = {'none':'unmark', 'unmark':'marked'}
return routes[status]
},
markUser1: function (item) {
item.status = this.getNextStatusBaseOnRoute(item.status)
},
markUser2: function (item) {
let status = item.name in this.selectedUsers ? this.selectedUsers[item.name] : 'none'
// remember to use vue.$set when adding new property to one object
this.$set(this.selectedUsers, item.name, this.getNextStatusBaseOnRoute(status))
}
}
})
.marked {
background-color:green;
}
.unmark {
background-color:yellow;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<h2>Case 1: </h2>
<div v-for="(item, index1) in users1" :key="'1'+index1">
<span>{{item.name}}:</span><span :class="[item.status]">{{item.status}}</span><button #click="markUser1(item)">Mark</button>
</div>
<h2>Case 2: </h2>
<div v-for="(item, index2) in users2" :key="'2'+index2">
<span>{{item.name}}:</span><span :class="[item.name in selectedUsers ? selectedUsers[item.name] : 'none']">{{item.name in selectedUsers ? selectedUsers[item.name] : 'none'}}</span><button #click="markUser2(item)">Mark</button>
</div>
</div>
For Vue3, you can also store the index of the selected element
<ul role="list" class="">
<li class="relative" v-for="(image, index) of images" :class="selectedImage == index? 'border-indigo-500 border-2': 'border-transparent'" >
<div #click="selectedImage = index" class="">
<img :src="image" alt="" class="object-cover pointer-events-none group-hover:opacity-75">
</div>
</li>
</ul>

Toggle Class for an item Vue.js

So I have a list of data brought in using vue resource from an api. I'm trying to integrate this list with bootstrap accordion.
So:
I have set this :
data: {
taller: false
}
and
<div class="panel panel-default"
v-repeat="faq: faqs | filterBy searchText"
v-transition="staggered"
stagger="200"
v-on="click: toggleHeight"
v-class="active: taller">
So on click i'll call toggleHeight and pass through the faq instance :
toggleHeight: function(faq) {
this.taller = true;
}
This function sets to taller to true however it sets it to true for all faq items, not just the one i've passed to toggle height.
How can I only return taller: true for clicked faq item?
Thanks
<div id="demo">
<div class="input-group">
<input class="form-control" v-model="searchText">
</div>
<script id="item-template" type="x-template">
<div
class="stag"
v-on="click: toggleHeight()"
v-class="active: taller"
>
<h3>{{ item.question }}</h3>
<h4>{{ item.answer }}</h4>
</div>
</script>
<question
v-repeat="item: items | filterBy searchText"
v-transition="staggered"
stagger="200">
</question>
</div>
</div>
and the js:
Vue.component('question', {
template: document.querySelector('#item-template'),
data: function() {
return {
taller: false
}
},
methods: {
toggleHeight: function() {
this.taller = ! this.taller
}
}
});
new Vue({
el: '#demo',
ready: function() {
this.fetchItems();
},
methods: {
fetchItems: function() {
this.$http.get('/dev/frequently-asked-questions/api/index', function(items) {
this.$set('items', items);
});
}
}
});
Had to make use of components to target each item more directly.
First of all, you only have one variable taller. Not one per faq. So you'd have to change it a bit to something like this:
new Vue({
el: '#faq-list',
data: {
faqs: [{id:'foo',taller:false},{id:'bar',taller:true}]
},
methods: {
toggleHeight: function (faq, ev) {
faq.taller = false;
ev.target.classList.add('active');
}
}
});
And your HTML to something like this:
<div id="faq-list">
<div class="panel panel-default"
v-repeat="faq: faqs"
v-on="click: toggleHeight (faq, $event)"
v-class="active: taller">
{{ faq.id }}</div>
</div>
And for fun, I added CSS to see things working:
.active {
color: red;
}
Here's the JSfiddle, for your reference and to mess around with it. Click on one of the items in the list and it turns red.

Categories