Toggle Class for an item Vue.js - javascript

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.

Related

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;
}

Vue Component, toggle data

Think i'm cracking up, this is some very basic stuff but it doesn't seem to be working...
Basically clicking the link should toggle display between true and false, but this isn't the case.
Vue.component('dropdown', {
props: [ 'expanded' ],
data: function() {
return {
display: !!(this.expanded)
}
},
template: '<div><transition name="expand"><slot :display="display"></slot></transition></div>'
});
window.app = new Vue({
el: '#app'
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<dropdown>
<div slot-scope="{ display }">
Toggle {{ display }}
<div v-if="display">
Dropdown content
</div>
</div>
</dropdown>
</div>
Edit:
Updated code, I forgot I changed that, I did infact have the click event as display = !display. But even with that said, if you had tried to click the button you would see that it doesn't change the true either...
Updating after a correcting comment from thanksd. I stumbled onto the right answer without really understanding it.
The problem is that within the slot, display refers to an item in the scope-slot object. Updating it there does not update the actual source variable. If you pass in and call a function, the proper variable is updated.
Vue.component('dropdown', {
props: ['expanded'],
data: function() {
return {
display: Boolean(this.expanded)
}
},
methods: {
toggle() {
this.display = !this.display;
}
},
template: '<div><transition name="expand"><slot :display="display" :toggle="toggle"></slot></transition></div>'
});
new Vue({
el: '#app'
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<dropdown>
<div slot-scope="{display, toggle}">
Toggle {{ display }}
<div v-if="display">
Dropdown content
</div>
</div>
</dropdown>
</div>
One solution would be to implement a v-model for the dropdown component which would allow you to two-way bind the display property to a property in the parent. That way you wouldn't need to pass anything via the slot-scope.
Here's an example of that:
Vue.component('dropdown', {
props: [ 'value' ],
data() {
return {
display: !!(this.value)
}
},
watch: {
value(value) {
this.$emit('input', value);
}
},
template: '<div><transition name="expand"><slot></slot></transition></div>'
});
new Vue({
el: '#app',
data() {
return { dropdownToggle: false }
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<dropdown v-model="dropdownToggle">
<div>
<a href="javascript:void(0)" #click="dropdownToggle = !dropdownToggle">
Toggle {{ dropdownToggle }}
</a>
<div v-if="dropdownToggle">
Dropdown content
</div>
</div>
</dropdown>
</div>

Vue emit ref of another element on click

Hello how i can get the element by className or something else. So i need to get element by className actually but i can't figure out how to do that in this case:
here is my fiddle: https://jsfiddle.net/cihanzengin/aq9Laaew/294154/
Simply i wanna do that: When click to menu in method i try to get element which have classname nav. I tried to make with ref but i can't
here is my code :
<div id="app">
<some-component #get-element="getElement"></some-component>
</div>
<script>
var someComponent = Vue.component("some-component", {
template: `
<div class="columns mobile-navigation">
<div class="column drawer">
<a class="is" #click="$emit('get-element')">MENU</a>
</div>
<div ref="nav" class="column mobile-nav-wrapper">
<p> Some Text </p>
</div>
</div>
`
});
var app = new Vue({
el: '#app',
data: {
},
components: {
"mobile-nav": mobileNav
},
methods: {
getElement() {
console.log(this.$refs.nav);
}
}
});
</script>
You can try something like this.
var classToCheck = 'myclass';
if( this.$refs.nav.classList.includes(classToCheck) ){
// includes above class
}
OR
myclickevent(event){
event.target.classList // this will get you classList of of clicked element then you can compare
}
This contains all the classes for your element
this.$refs.nav.classList
i found the solution:
here is need to add **<some-component>** to ref attribute for accessing to ref inside jsx.
Then will possible to access to nav ref with: this.$refs.someRef.$refs.nav
<div id="app">
<some-component #get-element="getElement" ref="someRef"></some-component>
</div>
<script>
var someComponent = Vue.component("some-component", {
template: `
<div class="columns mobile-navigation">
<div class="column drawer">
<a class="is" #click="$emit('get-element')">MENU</a>
</div>
<div ref="nav" class="column mobile-nav-wrapper">
<p> Some Text </p>
</div>
</div>
`
});
var app = new Vue({
el: '#app',
data: {
},
components: {
"mobile-nav": mobileNav
},
methods: {
getElement() {
console.log(this.$refs.someRef.$refs.nav);
}
}
});
</script>

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>

How to Add or Remove Vue.js component dynamically (programmatically or on the fly)

Here is my code, this is just a example code, if the below works then this will help me to build something else that I am working on.
<template>
<div id="wrapper">
<div id="divOne">
<!-- Add or remove component here dynamically -->
</div>
<div id="divTwo">
<!-- Add or remove component here dynamically -->
</div>
<!-- There will be more divs like #divOne #divTwo above -->
<div>
<input type="radio" id="one" value="divOne" v-model="pickedDiv">
<label for="one">One</label>
</div>
<div>
<input type="radio" id="two" value="divTwo" v-model="pickedDiv">
<label for="two">Two</label>
</div>
<button #click="addComponent">Add Component</button>
</div>
</template>
<script>
import SomeComponent from './SomeComponent'
export default {
data() {
return {
pickedDiv: '',
pickedDivPreviously: ''
propItems: ['item1', 'item2']
}
}
methods: {
addComponent () {
//-- This is not working code but I need something like this --//
this.pickedDivPreviously = this.pickedDiv // event not sure how to get previously selected div
const divThatIsPicked = document.getElementById(this.pickedDiv)
const divThatWasPickedPreviously = document.getElementById(this.pickedDivPreviously)
// code here to remove/empty/destroy old component from 'divThatWasPickedPreviously'
divThatWasPickedPreviously.innerHTML = ""
// code here to add new component in 'divThatIsPicked'
divThatIsPicked.appendChild('<some-component :someProp="propItems" #someEvent="someFn">')
}
}
}
</script>
I don't want to distract you from answering actual question but If you are curious about what I am working then check this :) Here I am trying to add new child DIV at the end of the row when any row item is clicked.
I will be more than happy if this is converted to vue than the actual question asked above, as said please don't get distracted from actual question if you find it hard :)
I got help from JamesThomson in forum.vuejs.org, though the solution did not fix my issue but I got to understand the limitation or possibilities of using Vue.js.
JamesThomson says:
Yeah, your example code definitely won’t work. When working with Vue
you need to think in a data oriented way, not DOM oriented (like
jQuery)
Taken from your SO post:
Here I am trying to add new child DIV at the end of the row when any
row item is clicked.
I assume this is your end goal for this topic. A simple example of
this can be achieved like so:
https://codepen.io/getreworked/pen/XZOgbm?editors=1010
let Welcome = {
template: `
<p #click="toggleMsg()">Welcome {{ msg }}!</p>
`,
data () {
return {
msg: 'home'
}
},
methods: {
toggleMsg () {
return this.msg = this.msg === 'home' ? 'back' : 'home';
}
}
}
const App = new Vue({
el: '#app',
data: {
children: [
Welcome
]
},
methods: {
add () {
this.children.push(Welcome);
},
}
});
<link rel="stylesheet" href="//cdn.rawgit.com/milligram/milligram/master/dist/milligram.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<template v-for="(child, index) in children">
<component :is="child" :key="child.name"></component>
</template>
<button #click="add()">Add Another</button>
</div>
or you can use a render function for more flexibility
https://jsfiddle.net/jamesbrndwgn/ku7m1dp0/9/
const Reusable = {
template: '<div>{{ name }} {{ bar }}</div>',
props: {
name: {
type: String
}
},
data () {
return {
bar: 'Bar'
}
}
}
const App = new Vue({
el: '#app',
data: {
items: []
},
methods: {
addComponent () {
const renderComponent = {
render (h) {
return h(Reusable, {
class: ['foo'],
props: {
name: 'Foo'
}
})
}
}
this.items.push(renderComponent)
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<link rel="stylesheet" href="//cdn.rawgit.com/milligram/milligram/master/dist/milligram.min.css">
<div id="app">
<component v-for="item in items" ref="itemRefs" :is="item" :key="item.name"></component>
<button #click="addComponent">Add Component</button>
</div>
I found one of the other ways that does kinda same as above but work only with old vue.js-1 not with vue.js-2:
var createNewBox = function() {
var MyPartial = Vue.extend({});
window.partial = new MyPartial({
template: '#partial',
data: function() {
return {
txt: 'This is partial'
}
},
methods: {
print: function() {
console.log('this.txt : ' + this.txt)
console.log('main.txt : ' + main.txt)
},
},
})
window.partial.$mount().$appendTo('body')
}
window.main = new Vue({
el: '#main',
data: function() {
return {
txt: 'This is main'
}
},
methods: {
show: function() {
createNewBox()
}
},
})
<script src="https://cdn.bootcss.com/vue/1.0.17/vue.min.js"></script>
<div #click="show" style="width:200px;height:200px;background:#000" id="main">
<template id="partial">
<div style="width:100px;height:100px;background:#ff0" #click.stop="print"></div>
</template>
</div>
this converted to Vue.
https://codepen.io/jacobgoh101/pen/Kojpve
<div id="app">
<div class="parent">
<div class="child" #click="handleChildClick" data-new-child-id="1">1234</div>
<div class="child" #click="handleChildClick" data-new-child-id="2">12341234 </div>
<div class="child" #click="handleChildClick" data-new-child-id="3">123412341234</div>
<div class="child" #click="handleChildClick" data-new-child-id="4">1234</div>
<div class="new-child" v-if="[1,2,3,4].indexOf(showNewChild) > -1">boom</div>
<div class="child" #click="handleChildClick" data-new-child-id="5">12341234</div>
<div class="child" #click="handleChildClick" data-new-child-id="6">123412341234</div>
<div class="child" #click="handleChildClick" data-new-child-id="7">1234</div>
<div class="child" #click="handleChildClick" data-new-child-id="8">12341234</div>
<div class="new-child" v-if="[5,6,7,8].indexOf(showNewChild) > -1">boom</div>
<div class="child" #click="handleChildClick" data-new-child-id="9">123412341234</div>
<div class="new-child" v-if="[9].indexOf(showNewChild) > -1">boom</div>
</div>
</div>
Javascript
new Vue({
el: '#app',
data: {
showNewChild:null
},
methods: {
handleChildClick(e) {
let id = e.target.dataset.newChildId;
id = Number(id);
this.showNewChild = id;
}
}
})

Categories