How to change the target button class? - javascript

i'm quite new to vue and can't figure out how to do it, i have several buttons and i need to select only one when clicked, i did it via
:class="isActive ? 'on' : 'off'"
v-on:click ="isActive = !isActive"
but this activates all the buttons, then I understand that I need to somehow distinguish the target button from the non-target one, but I can’t figure out how to do this. I can't find suitable implementation examples can you provide code examples
data() {
return {
isActive: true,
color: ‘’,
};
},
<template>
<div id="btn-box">
<button
type="button"
class="btn off"
#click="component='BorderLeftComonent', toggleShowPopup()">
<div
style="padding: 0 5px; width: 25px; margin: 0 auto; font-size: 25px;"
:style="{ 'border-left': `4px solid ${color}` }">A</div>
</button>
<button
type="button"
class="btn off"
#click="component='TextBalloonComponent'">
<div
class="bubble"
style="margin: 0 auto; width: 25px; font-size: 25px;">A</div>
</button>
<button
type="button"
class="btn off"
#click="component='DashedComponent'">
<div
style="border: 4px dashed #f5d018; margin: 0 auto; width: 45px; font-size: 25px;">A</div>
</button>
</div>
</template>

Wrong Syntax:
:class="isActive ? 'on' : 'off'"
v-on:click ="isActive = !isActive"
Correct Syntax (using shorthand):
:class="{ 'on': isActive, 'off': !isActive }"
#click="isActive = !isActive"
OR this, and take a look at the example below:
:class="{ 'on': isActive, 'off': !isActive }"
#click="toggle"
Define the #click event toggle in the methods as this:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<!-- these buttons have `:class` binding and `#click` event -->
<button :class="{ 'on': isActive, 'off': !isActive }" #click="isActive = !isActive">Toggle</button> OR
<button :class="{ 'on': isActive, 'off': !isActive }" #click="toggle">Toggle</button> OR
<!-- these buttons just have `#click` event -->
<button #click="isActive = !isActive">Toggle</button> OR
<button #click="toggle">Toggle</button> OR
<!-- these buttons do not (have class and event binding) do anything -->
<button>Toggle</button> OR
<button>Toggle</button>
<p v-if="isActive">{{ message }}</p>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
isActive: true,
},
methods: {
toggle() {
this.isActive = this.isActive ? false : true
}
},
})
</script>
<style scoped>
/*simply styling your classes*/
button {
padding: 0.5em;
border-radius: 5px;
color: hsl(205, 46.5%, 73.5%);
background-color: hsl(205, 14%, 28%);
}
.on {
color: green;
}
.off {
color: red;
}
</style>

Use v-for directive to iterate over an array of button objects where each object includes it's own isActive property that can be toggled by the onclick event.
<button
v-for="(button, index) in buttons"
:key="index"
:class="button.isActive ? 'on' : 'off'"
#click="button.isActive = !button.isActive"
>
<div :class="`btn btn-${button.type}`">{{ button.label }}</div>
</button>
data() {
return {
buttons: [
{
label: "A",
isActive: false,
type: "border-left",
},
{
label: "A",
isActive: false,
type: "text-balloon",
},
{
label: "A",
isActive: false,
type: "dashed",
},
],
};
}
<style scoped>
.btn {
padding: 0 5px;
width: 25px;
margin: 0 auto;
font-size: 25px;
}
.btn-border-left {
border-left: 4px solid #f55;
}
.btn-dashed {
border: 4px dashed #f5d018;
width: 45px;
}
</style>

It seems that you need a button group, a classic component in UI libraries.
Have a look at this one for example.
For example below, you have 4 buttons next to each other, and each button is highlighted when you click on it, see gif below.
And in your code, you have access to a property (here "text") that reflects which button is selected.
Code taken from the link above:
<v-btn-toggle
v-model="text"
tile
color="deep-purple accent-3"
group
>
<v-btn value="left">
Left
</v-btn>
<v-btn value="center">
Center
</v-btn>
<v-btn value="right">
Right
</v-btn>
<v-btn value="justify">
Justify
</v-btn>
</v-btn-toggle>
Does that answer your question?

Related

How to pass a style property to a child component as a computed property in Vue.js?

I have the following problem:
I have too much logic in my inline style and would to place it inside a computed property. I know, that this is the way, that I should go, but do not know, how to achieve it.
Below I a simple example that I made for better understanding. In it, on button press, the child's component background-color is changing.
My code can be found here: Codesandbox
My parent component:
<template> <div id="app">
<MyChild :colorChange="active ? 'blue' : 'grey'" />
<p>Parent:</p>
<button #click="active = !active">Click Me!</button> </div> </template>
<script> import MyChild from "./components/MyChild";
export default { name: "App", components: {
MyChild, }, data() {
return {
active: false,
}; }, }; </script>
and my child component:
<template> <div class="hello">
<p>Hello from the child component</p>
<div class="myDiv" :style="{ background: colorChange }">
here is my color, that I change
</div> </div> </template>
<script> export default { name: "HelloWorld", props: {
colorChange: {
type: String,
default: "green",
}, }, }; </script>
<!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .myDiv { color: white; padding: 1rem 0; } </style>
I also have a second question. Let's say, that I have more than one child component and also would like to change to colors on button click, but those colors differ. How can I achieve it without repeating myself (within the computed properties?)
Code example for my parent component:
<MyChild :colorChange="active ? 'blue' : 'grey'" />
<MyChild :colorChange="active ? 'grey' : 'blue'" />
<MyChild :colorChange="active ? 'blue' : 'red'" />
<MyChild :colorChange="active ? 'yellow' : 'blue'" />
Thanks in advance!
Maybe You can bind class and use different css classes:
Vue.component('MyChild',{
template: `
<div class="hello">
<p>Hello from the child component</p>
<div class="myDiv" :class="collorCurrent">
here is my color, that I change
</div>
</div>
`,
props: {
colorChange: {
type: String,
default: "green",
},
colorDef: {
type: String,
default: "green",
},
isActive: {
type: Boolean,
default: false,
},
},
computed: {
collorCurrent() {
return this.isActive ? this.colorChange : this.colorDef
}
}
})
new Vue({
el: "#demo",
data() {
return {
active: false,
}
},
})
.myDiv { color: white; padding: 1rem; }
.blue {
background: blue;
font-size: 22px;
}
.red {
background: red;
font-variant: small-caps;
}
.yellow {
background: yellow;
color: black;
}
.grey {
background: grey;
text-decoration: underline;
}
.green {
background: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<p>Parent:</p>
<button #click="active = !active">Click Me!</button>
<my-child :is-active="active" :color-def="'grey'" :color-change="'blue'"></my-child>
<my-child :is-active="active" :color-def="'blue'" :color-change="'grey'"></my-child>
<my-child :is-active="active" :color-def="'red'" :color-change="'blue'"></my-child>
<my-child :is-active="active" :color-def="'blue'" :color-change="'yellow'"></my-child>
</div>

Enable / Disable Vue Transitions based on boolean

In Vue, can I somehow disable a transition-animation, based on a boolean?
So now the animation is enabled:
<transition name="fadeUp">
<div v-if="elIsVisible">
<p>Foo Bar</p>
</div>
</transition>
But I wish I do something like this:
<transition name="fadeUp" animation-enabled="false">
<div v-if="elIsVisible">
<p>Foo Bar</p>
</div>
</transition>
Or perhaps a smart work-around?
It's a for module-based website (one component per block), where it could be nifty if the user could enable/disable the animation for certain blocks.
There's a workaround, yes. You can skip the CSS detection by setting v-bind:css to false on the <transition> component.
new Vue({
el: '#app',
data: () => ({
show: true,
animated: true
})
})
.fade-enter-active,
.fade-leave-active {
transition: opacity .5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
p {
background-color: beige;
border: 1px solid orange;
padding: 4px 6px;
}
button {
display: block;
margin-top: 2em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<transition name="fade" :css="animated">
<p v-if="show">Hey, there!</p>
</transition>
<label>
<input type="checkbox" v-model="animated" />
Animated
</label>
<button #click="show = !show">Toggle visibility</button>
</div>
You can bind :name in <transition>, which if set to empty, no transition is applied (like when no name is given).
<transition :name="transitionName">
<div v-if="elIsVisible">
<p>Foo Bar</p>
</div>
</transition>
<select #change="e => { transitionName = e.target.value }">
<option
v-for="transition in ['fadeUp', '']"
:key="transition"
:value="transition"
>
{{ transition }}
</option>
</select>

Vue 2 event listener on component root

I'm trying to capture an event on the component root node, but the following does not work. I don't want to just listen on a node in the component. I want to be able to click on any element and then hit backspace to remove it. The code below is a basic example of how I setup my code.
<template>
<div v-on:keydown.delete="delete()">
<img id="image" src="..." v-on:click="set_active()">
</div>
</template>
<script>
export default {
return {
data() {
active: ''
},
methods: {
delete(){
delete this.$refs[this.active][0];
},
set_active() {
this.active = event.target.getAttribute('id');
}
}
}
}
</script>
After doing some tests, here is what I discovered:
Having a method called delete won't work. I don't know why, the question remains unanswered here. Rename it to remove, for example.
When trying to catch keyboard events on a div, you may need to add a tabindex attribute for it to work. (See here)
Interactive demo
Vue.component('my-component', {
template: '#my-component',
data() {
return {
images: [
"https://media.giphy.com/media/3ohs7KtxtOEsDwO3GU/giphy.gif",
"https://media.giphy.com/media/3ohhwoWSCtJzznXbuo/giphy.gif",
"https://media.giphy.com/media/8L0xFP1XEEgwfzByQk/giphy.gif"
],
active: null
};
},
methods: {
set_active(i) {
this.active = i;
},
remove() {
if (this.active !== null) {
this.images = this.images.filter((_, i) => i !== this.active);
this.active = null;
}
}
}
});
var vm = new Vue({
el: '#app'
});
div {
outline: none; /* Avoid the outline caused by tabindex */
border: 1px solid #eee;
}
img {
height: 80px;
border: 4px solid #eee;
margin: .5em;
}
img:hover {
border: 4px solid #ffcda9;
}
img.active {
border: 4px solid #ff7c1f;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.min.js"></script>
<div id="app">
<my-component></my-component>
</div>
<template id="my-component">
<div #keydown.delete="remove" tabindex="0">
<img
v-for="(img, i) in images"
:key="i"
:src="img"
:class="{ active: active === i }"
#click="set_active(i)"
/>
</div>
</template>

How to use transitions on select option using Vue.js

I have quick question regarding transitions with Vue.js.
In my boostrap template I'm trying to add one more select option dropdown based on selected option. So, I added change event on first select option. So, if I select 'first item' then change classes and add dropdown in row, or else hides it.
Something like this:
selectTodo: function(e) {
let selectValue = e.target.options[e.target.selectedIndex].text
if (selectValue === 'Learn Vue') {
this.styleObject.display = 'unset';
this.col_md = 'form-group col-md-4';
this.showCropStageList = true;
}
else {
this.showCropStageList = false;
this.styleObject.display = 'none';
this.col_md = 'form-group col-md-6';
this.cropStageList = null;
}
}
I managed to do that but I want more smoother transitions on the first two dropdowns.
I've managed to set up some fiddle where you can see smooth slide transition for third select dropdown but if if I change select from "Learn Vue" to something else, the third dropdown hides it but without any animation as well.
You can see it in the snippet below, too!
new Vue({
el: "#app",
data: {
todos: [
{ id:1 ,text: "Learn JavaScript", done: false },
{ id:2 ,text: "Learn Vue", done: false },
{ id:3 ,text: "Play around in JSFiddle", done: true },
{ id:4 ,text: "Build something awesome", done: true }
],
col_md: 'form-group col-md-6',
styleObject: {
display: 'none'
},
showCropStageList: false,
},
methods: {
toggle: function(todo){
todo.done = !todo.done
},
selectTodo: function(e) {
let selectValue = e.target.options[e.target.selectedIndex].text
if (selectValue === 'Learn JavaScript') {
this.styleObject.display = 'unset';
this.col_md = 'form-group col-md-4';
this.showCropStageList = true;
}
else {
this.showCropStageList = false;
this.styleObject.display = 'none';
this.col_md = 'form-group col-md-6';
this.cropStageList = null;
}
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
.fade-enter {
opacity: 0;
}
.fade-enter-active {
transition: opacity 1s;
}
.fade-leave {}
.fade-leave-active {
transition: opacity 1s;
opacity: 0;
}
#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://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div class="container">
<!-- Content here -->
<div id="app">
<div class="form-row">
<transition name="fade" appear>
<div v-bind:class=col_md>
<label for="cropType" class="col-form-label-sm font-weight-bold">Select Learn Javascript </label>
<select class="form-control" v-on:change="selectTodo" id="cropType" v-model="pickedCropType" #change="getCropsByType()">
<option v-for="(todo, index) in todos" :key="index" :value="todo.id" >
{{todo.text}}
</option>
</select>
</div>
</transition>
<div v-bind:class=col_md>
<label for="cropCulture" class="col-form-label-sm font-weight-bold">2. Second</label>
<select class="form-control" id="cropCulture">
<option v-for="(todo, index) in todos" :key="index" :value="todo.id" >
{{todo.text}}
</option>
</select>
</div>
<transition
enter-active-class="animated fadeInLeft"
leave-active-class="animated fadeOutLeft"
>
<div class="form-group col-md-4" v-if="showCropStageList" v-bind:class="{styleObject }">
<label for="cropStage" class="col-form-label-sm font-weight-bold">3. Third</label>
<select class="form-control" id="cropStage">
<option v-for="(todo, index) in todos" :key="index" :value="todo.id" >
{{todo.text}}
</option>
</select>
</div>
</transition>
</div>
</div>
</div>
You can see change class for first two dropdowns but without any transitions. So, is it possible to add some transitions for first two dropdowns?
EDIT :
As you can see I'm changing class from *form-group col-md-6* to *form-group col-md-4* and I' wondering if is it possible that that tranisition from md-6 to md-4 can be a bit smoother.
https://jsfiddle.net/Loque/akt0su98/
In your codes, you directly update the class from col-md-4 to col-md-6 in selectTodo. It will cause first two <div> take full width of the row before 3rd div finishes its animation.
The solution, uses this.$nextTick and setTimeout.
when add the third div, delay execute show the third div before first two <div> finish their animations.
when remove the third div, delay apply col-md-6 to the first two <div> before the third <div> finishes its animations.
Below is one sample: (PS: I used transition-group here, because it will be better for Reusable Transitions)
Warning: Please test below demo under full page, otherwise bootstrap will treat it as extre-small screen (place each <div> in each row), though I think the effects in small screen is still not bad.
new Vue({
el: "#app",
data: {
todos: [
{ id:1 ,text: "Learn JavaScript", done: false },
{ id:2 ,text: "Learn Vue", done: false },
{ id:3 ,text: "Play around in JSFiddle", done: true },
{ id:4 ,text: "Build something awesome", done: true }
],
col_md: 'form-group col-md-6',
styleObject: {
display: 'none'
},
showCropStageList: false,
pickedCropType: ''
},
methods: {
getCropsByType: function () {},
toggle: function(todo){
todo.done = !todo.done
},
selectTodo: function(e) {
let selectValue = e.target.options[e.target.selectedIndex].text
if (selectValue === 'Learn Vue') {
this.col_md = 'form-group col-md-4';
this.$nextTick(() => { //delay display the third div before first two div finish animation
setTimeout(()=>{
this.styleObject.display = 'unset';
this.showCropStageList = true;
}, 500)
})
}
else {
this.showCropStageList = false;
this.styleObject.display = 'none';
this.$nextTick(() => { //delay apply `md-6` to first two div before 3rd div finish animation
setTimeout(()=>{
this.col_md = 'form-group col-md-6';
}, 1000)
})
this.cropStageList = null;
}
}
}
})
.sample {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
.fade-item {
transition: all 0.5s;
display:inline-block;
}
.fade-enter {
opacity: 0;
transform: translateX(-130px);
}
.fade-enter-active {
transition: all 1.5s;
}
.fade-leave-to {
transform: translateX(130px);
opacity: 0;
flex: 0 0 20%;
}
.fade-leave-active {
transition: all 1s;
}
#app {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<!-- Content here -->
<div id="app">
<div class="container">
<div class="sample">
<transition-group name="fade" tag="div" class="form-row">
<div v-bind:class="col_md" :key="1" class="fade-item">
<label for="cropType" class="col-form-label-sm font-weight-bold">Select Learn Javascript </label>
<select class="form-control" v-on:change="selectTodo" id="cropType" v-model="pickedCropType" #change="getCropsByType()">
<option v-for="(todo, index) in todos" :key="index" :value="todo.id" >
{{todo.text}}
</option>
</select>
</div>
<div v-bind:class="col_md" :key="2" class="fade-item">
<label for="cropCulture" class="col-form-label-sm font-weight-bold">2. Second</label>
<select class="form-control" id="cropCulture">
<option v-for="(todo, index) in todos" :key="index" :value="todo.id" >
{{todo.text}}
</option>
</select>
</div>
<div class="form-group col-md-4 fade-item" v-if="showCropStageList" v-bind:class="{styleObject }" :key="3">
<label for="cropStage" class="col-form-label-sm font-weight-bold">3. Third</label>
<select class="form-control" id="cropStage">
<option v-for="(todo, index) in todos" :key="index" :value="todo.id" >
{{todo.text}}
</option>
</select>
</div>
</transition-group>
</div>
</div>
</div>
I don't know if there is a specific way to do in Vue but what you want can be easily achieved using some CSS magic. It is simple in a matter of fact, all you are going to do is add a class to your changing div let's say for example the class name is transition and in your CSS you define transition styles as follow:
.transition {
transition: all 0.25s linear;
}
You can find an updated fiddle here
Note you can change the timing function linear and the time 0.25s to suit your needs, you can read more about CSS transitions and effects here

VueJS - input file repeater

I want delete item from array but there are the same item and js delete last one !
let app = new Vue({
el: '#app',
data: {
items: []
},
methods: {
addItem() {
this.items.push('');
},
removeItem(index) {
this.items.splice(index, 1);
}
}
});
<script src="https://unpkg.com/vue#2.1.10/dist/vue.js"></script>
<div id="app">
<ul class="list-group">
<li class="list-group-item" v-for="(item , index) in items">
remove
<input name="form[]" type='file'>
</li>
</ul>
<button #click='addItem'>new item</button>
</div>
JSFiddle: https://jsfiddle.net/6hvbqju2/
Vue uses an "in-place patch strategy" when dealing with list of elements. This strategy is not suitable when relying on form input values.
When using v-for directive it is better to define a v-bind:key to give Vue a hint to track each node.
We'll store numbers in the items array and use them as a key. In your case you should use an item's property that can serve as a unique key.
let app = new Vue({
el: '#app',
data: {
counter: 0,
items: []
},
methods: {
addItem() {
this.items.push(this.counter++);
},
removeItem(index) {
this.items.splice(index, 1);
}
}
});
<script src="https://unpkg.com/vue#2.1.10/dist/vue.js"></script>
<div id="app">
<ul class="list-group">
<li class="list-group-item" v-for="(item , index) in items" :key="item">
remove
<input name="form[]" type='file'>
</li>
</ul>
<button #click='addItem'>new item</button>
</div>
Your codes working fine but,
This is because of file input auto complete behaviour
See this example
let app = new Vue({
el : '#app',
data : {
items: [],
},
methods : {
addItem() {
this.items.push({file: null});
console.log(this.items)
},
removeItem(index) {
this.items.splice(index,1);
},
handleChange(item, event){
item.file = event.target.files["0"];
}
}
});
.upload-btn-wrapper {
position: relative;
overflow: hidden;
display: inline-block;
vertical-align: middle;
}
.btn {
border: 1px solid gray;
color: gray;
background-color: white;
padding: 4px 10px;
border-radius: 4px;
font-size: 15px;
font-weight: bold;
}
.upload-btn-wrapper input[type=file] {
font-size: 100px;
position: absolute;
left: 0;
top: 0;
opacity: 0;
}
<script src="https://unpkg.com/vue#2.1.10/dist/vue.js"></script>
<div id="app">
<ul class="list-group">
<li class="list-group-item" v-for="(item , index) in items">
remove
<div type="button" class="upload-btn-wrapper">
<button class="btn">{{ item.file ? item.file.name : 'Choose File' }}</button>
<input name="form[]" type="file" #change="handleChange(item, $event)">
</div>
</li>
</ul>
<button #click='addItem'>new item</button>
</div>

Categories