Add class to unselected item in VueJS 2 - vuejs2

I tried setting an active state whenever I select an item and add an active class to it. Now, I wonder how can we get the unselected item and add a specific class to it like not-active.
Sample:
<img src="/images/icons/thumbup.png"
#click="setActive('thumbUp')"
class="thumb-active"
:class="{ active: isActive('thumbUp') }">
<img src="/images/icons/thumbdown.png"
#click="setActive('thumbDown')"
class="thumb-active"
:class="{ active: isActive('thumbDown') }">
Vue script:
export default {
data() {
return {
activeItem: '',
}
},
methods: {
isActive: function (button) {
return this.activeItem === button
},
setActive: function (button) {
this.activeItem = button
}
},
}

Very, very simply
:class="{ active: isActive('state A'), 'not-active': isActive('state B') }"
Here's a demo...
var app = new Vue({
el: '#app',
data: {
activeItem: null
},
methods: {
isActive: function (button) {
return this.activeItem === button
},
setActive: function (button) {
this.activeItem = button
}
}
})
.active {
background-color: green;
color: white;
font-weight: bold;
}
.not-active {
background-color: red;
color: white;
}
button {
font-size: 2em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<div id="app">
<button src="/images/icons/thumbup.png"
#click="setActive('thumbUp')"
class="thumb-active"
:class="{ active: isActive('thumbUp'), 'not-active': isActive('thumbDown') }">
Thumbs Up!
</button>
<button src="/images/icons/thumbdown.png"
#click="setActive('thumbDown')"
class="thumb-active"
:class="{ active: isActive('thumbDown'), 'not-active': isActive('thumbUp') }">
Thumbs Down¡
</button>
</div>

Related

nuxtjs add and remove class on click on elements

I am new in vue and nuxt and here is my code I need to update
<template>
<div class="dashContent">
<div class="dashContent_item dashContent_item--active">
<p class="dashContent_text">123</p>
</div>
<div class="dashContent_item">
<p class="dashContent_text">456</p>
</div>
<div class="dashContent_item">
<p class="dashContent_text">789</p>
</div>
</div>
</template>
<style lang="scss">
.dashContent {
&_item {
display: flex;
align-items: center;
}
&_text {
color: #8e8f93;
font-size: 14px;
}
}
.dashContent_item--active {
.dashContent_text{
color:#fff;
font-size: 14px;
}
}
</style>
I tried something like this:
<div #click="onClick">
methods: {
onClick () {
document.body.classList.toggle('dashContent_item--active');
},
},
but it changed all elements and I need style change only on element I clicked and remove when click on another
also this code add active class to body not to element I clicked
This is how to get a togglable list of fruits, with a specific class tied to each one of them.
<template>
<section>
<div v-for="(fruit, index) in fruits" :key="fruit.id" #click="toggleEat(index)">
<span :class="{ 'was-eaten': fruit.eaten }">{{ fruit.name }}</span>
</div>
</section>
</template>
<script>
export default {
name: 'ToggleFruits',
data() {
return {
fruits: [
{ id: 1, name: 'banana', eaten: false },
{ id: 2, name: 'apple', eaten: true },
{ id: 3, name: 'watermelon', eaten: false },
],
}
},
methods: {
toggleEat(clickedFruitIndex) {
this.fruits = this.fruits.map((fruit) => ({
...fruit,
eaten: false,
}))
return this.$set(this.fruits, clickedFruitIndex, {
...this.fruits[clickedFruitIndex],
eaten: true,
})
},
},
}
</script>
<style scoped>
.was-eaten {
color: hsl(24, 81.7%, 49.2%);
}
</style>
In Vue2, we need to use this.$set otherwise, the changed element in a specific position of the array will not be detected. More info available in the official documentation.

How to intercept or clear v-model value after #paste event in Vue 2.6

I have a problem with the v-model and #paste event on an input field.
When I copy something and paste it into the input field, it shows me the copied value in the input field too.
I would like to prevent this.
I have created a simple JsFiddle Todo App to show the problem.
https://jsfiddle.net/k12drcqn/1/
onPaste: function() {
let clipped = event.clipboardData.getData('text').split("\n");
clipped.forEach(item => {
this.todos.push({
text: item, done: false
})
})
// is not clearing the v-model: todo
this.todo = ''
}
For example if you copy something like this into the input field:
Task1
Task2
Task3
These tasks will be added to the list but also displayed in the input field.
Is there a possibility not to display in the input field the pasted tasks ?
onPaste: function() {
let clipped = event.clipboardData.getData('text').split("\n");
clipped.forEach(item => {
this.todos.push({
text: item, done: false
})
})
// instead of this
// this.todo = ''
// make this
setTimeout(() => {
this.todo = ''
}, 0);
}
I think the text stays in the input, when calling this.todo = '' synchronously.
The todo property is cleared successfully, the issue in the paste event, you should clear it by adding the blur event :
onPaste: function(event){
let clipped = event.clipboardData.getData('text').split("\n");
clipped.forEach(item => {
this.todos.push({
text: item, done: false
})
})
this.todo = ''
event.target.blur();
}
Full example
new Vue({
el: "#app",
data: {
todo: '',
todos: [{
text: "Learn JavaScript",
done: false
},
{
text: "Learn Vue",
done: false
},
{
text: "Play around in JSFiddle",
done: true
},
{
text: "Build something awesome",
done: true
}
]
},
methods: {
toggle: function(todo) {
todo.done = !todo.done
},
addTodo: function() {
this.todos.push({
text: this.todo,
done: false
})
this.todo = ''
},
onPaste: function(event) {
let clipped = event.clipboardData.getData('text').split("\n");
clipped.forEach(item => {
this.todos.push({
text: item,
done: false
})
})
// is not clearing the v-model: todo
this.todo = ''
event.target.blur();
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Todos:</h2>
<input type="text" v-model="todo" #keyup.enter="addTodo" #paste="onPaste">
<ol>
<li v-for="todo in todos">
<label>
<input type="checkbox"
v-on:change="toggle(todo)"
v-bind:checked="todo.done">
<del v-if="todo.done">
{{ todo.text }}
</del>
<span v-else>
{{ todo.text }}
</span>
</label>
</li>
</ol>
</div>

How to make the props binding reactive when using 'createElement' function

Vue.config.devtools = false
Vue.config.productionTip = false
let modal = Vue.extend({
template: `
<div class="modal">
<p>Balance: {{ balance }}</p>
<input #input="$emit('input', $event.target.value)" :value="value">
<button #click="$emit('input', balance)">ALL</button>
</div>
`,
props: ['balance', 'value']
})
function makeComponent(data) {
return { render(h) { return h(modal, data) } }
}
Vue.component('app', {
template: `
<div>
<p>Balance: {{ balance }}</p>
<p>To withdraw: {{ withdrawAmount }}</p>
<p>Will remain: {{ balance - withdrawAmount }}</p>
<button #click="onClick">Withdraw</button>
<modal-container ref="container"/>
</div>`,
data () {
return {
withdrawAmount: 0,
balance: 123
}
},
methods: {
onClick () {
this.$refs.container.show(makeComponent({
props: {
balance: String(this.balance),
value: String(this.withdrawAmount)
},
on: {
input: (value) => {
this.withdrawAmount = Number(value)
}
}
}))
}
}
})
Vue.component('modal-container', {
template: `
<div>
<component v-if="canShow" :is="modal"/>
</div>
`,
data () {
return { modal: undefined, canShow: false }
},
methods: {
show (modal) {
this.modal = modal
this.canShow = true
},
hide () {
this.canShow = false
this.modal = undefined
}
}
})
new Vue({
el: '#app'
})
.modal {
background-color: gray;
width: 300px;
height: 100px;
margin: 10px;
padding: 10px;
}
* {
font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
color: #2c3e50;
line-height: 25px;
font-size: 14px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17-beta.0/vue.js"></script>
<div id="app">
<app></app>
</div>
This is a simplified version of our application using vue-js-modal. The basic Idea is that we pass a component declaration along with required VNodeData to the plugin function and it would append that component to a pre-defined DOM point with proper data bindings.
And our usecase is that:
User clicks 'Withdraw'
Modal pops up with an input field and static field displaying user's balance
User enters the amount he/she wants to withdraw and that amount and resulting balance are displayed in the callee component.
There is a 'ALL' button besides the input field for user to easily enter a number equal to his/her balance
Problem: When user clicks 'ALL', the 'modal' component fires an 'input' event with value of the balance and the callee component receives that event and updates its 'withdrawAmount'. But the 'withdrawAmount' is supposed to be passed back to the 'modal' as 'value' prop and further updates the input field, which doesn't happen.

Accessing Vue Component scope from external javascript

Can I access Vue components data properties and methods from external javascript? I am trying to create a hybrid application where a portion of the screen is a Vue component, and I want to call a method inside that component on click of a button which is handled by pure js. Is there a way to achieve this?
Thanks!
Yes. You need to assign your Vue object to a variable (i.e. vue) and then you can access vue.methodName() and vue.propertyName:
// add you new Vue object to a variable
const vue = new Vue({
el: "#app",
data: {
todos: [
{ text: "Learn JavaScript", done: false },
{ text: "Learn Vue", done: false },
{ text: "Play around in JSFiddle", done: true },
{ text: "Build something awesome", done: true }
]
},
methods: {
toggle: function(todo){
todo.done = !todo.done
}
}
});
// Add event listener to outside button
const button = document.getElementById('outsideButton');
button.addEventListener('click', function() {
vue.toggle(vue.todos[1]);
});
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.min.js"></script>
<div id="app">
<h2>Todos:</h2>
<ol>
<li v-for="todo in todos">
<label>
<input type="checkbox"
v-on:change="toggle(todo)"
v-bind:checked="todo.done">
<del v-if="todo.done">
{{ todo.text }}
</del>
<span v-else>
{{ todo.text }}
</span>
</label>
</li>
</ol>
</div>
<div class="outside">
<button id="outsideButton">
Click outside button
</button>
</div>
Yes, you can add an event listener to the Vue component that listens for the button click's event. See example here on codepen.
JS
new Vue({
el: '#app',
methods: {
clickMethod(event){
if (event.target.id === 'outsideButton') {
alert('button clicked')
}
}
},
created(){
let localThis = this
document.addEventListener('click', this.clickMethod)
}
})
HTML
<div>
<button id="outsideButton">Button</button>
<div id="app">
</div>
</div>

Vue js toogle the items in the list

I wanted to create a side menu bar. I have added a class show when the li should be shown.
But in my case what happen is when one item is shown(1st) and next(2nd) item is clicked, the 1st get collapsed(which is fine) but the 2nd do not show up immediately.
new Vue({
el: '#app',
methods: {
setActiveItemId(itemIndex) {
this.activeItemId = itemIndex
this.isActive = !this.isActive
}
},
data: {
message: 'Hello Vue.js!',
isActive: false,
activeItemId: '',
sideBar: [{
name: "Dashboard",
url: "/dashboard",
icon: "ti-world",
children: [{
name: "Buttons",
url: "/components/buttons",
icon: "fa-book",
},
{
name: "Social Buttons",
url: "/components/social-buttons",
icon: "icon-puzzle",
}
]
},
{
name: "Components",
url: "/components",
icon: "ti-pencil-alt",
children: [{
name: "Buttons",
url: "/components/buttons",
icon: "fa-book",
},
{
name: "Social Buttons",
url: "/components/social-buttons",
icon: "icon-puzzle",
}
]
}
]
}
})
.collapse.show {
display: block;
}
.collapse {
display: none;
}
.list-unstyled {
padding-left: 0;
list-style: none;
}
.collapse.list-unstyled {
padding-left: 15px;
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
<ul class="list-unstyled">
<li>
<a>
<i class="ti-home"></i>Home</a>
</li>
<li v-for="(x, itemIndex) in sideBar" :key="itemIndex">
<a #click="setActiveItemId(itemIndex)">
<i class="fa" :class="x.icon"></i>{{x.name}}
</a>
<ul :id="x.id" class="collapse list-unstyled" :class="{'show':activeItemId === itemIndex && isActive}">
<li v-for="y in x.children" :key="y.id">
<a>{{y.name}}</a>
</li>
</ul>
</li>
</ul>
</div>
So, How can i collapse the 1st and display the 2nd immediately, when 2nd item is clicked?
Fiddle
The problem is that inside your setActiveItemId method you are always toggling the isActive state, regardless of which item is being activated. That means that it toggles the same item, but when jumping to another you'll have to click twice. I'd take a different approach, where isActive is a computed property instead of residing in the data.
// ...
methods: {
setActiveItemId(itemIndex) {
// If item is currently selected, toggle
if (itemIndex === this.activeItemId) {
this.activeItemId = ''
return
}
this.activeItemId = itemIndex
}
},
computed: {
isActive () {
return this.activeItemId !== ''
}
}
Here's the updated fiddle:
https://jsfiddle.net/2ytuL46c/3/
Unrelated but worth noting: remember that your data must be a function that returns the data object, and not an object itself.