i am trying to apply animation width:20% on wrong class when v-if condition becomes false , how can i watch v-if change .
export default {
data() {
return {
ok: true
}
},
methods: {
toggle() {
this.ok = !this.ok;
}
},
watch: {
ok() {
// apply animation after v-if is false
}
}
}
.wrong {
background-color: #fdd;
transition: width 2s;
width: 100%;
}
<template>
<div class="view1">
<div v-if="ok">
<p class="right">OK</p>
</div>
<div v-else>
<p class="wrong">NO</p>
</div>
</div>
<button #click="toggle">Toggle</button>
</template>
You can do this with CSS rather than jQuery. The trick is to have a width transition, and a delay in applying the width style. You listen to a change by using a watch.
new Vue({
el: '.view1',
data: {
ok: true,
delayedReaction: null
},
methods: {
toggle() {
this.ok = !this.ok;
}
},
watch: {
ok() {
if (this.ok) {
this.delayedReaction = null;
} else {
// nextTick didn't suffice
setTimeout(() => {
this.delayedReaction = {
width: '20%'
};
}, 0);
}
}
}
});
.wrong {
background-color: #fdd;
transition: width 2s;
width: 100%;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div class="view1">
<div v-if="ok">
<p class="right">OK</p>
</div>
<div v-else>
<p class="wrong" :style="delayedReaction">NO</p>
</div>
<button #click="toggle">Toggle</button>
</div>
Related
I have a refresh button on dashboard. I want to make it rotate 360 degrees.
How can I rotate image with every click on a refresh button?
This is the code I tried, it only work twice click:
var vm = new Vue({
el: '#app',
data: {
clicked: false,
},
methods: {
rotation() {
this.clicked = !this.clicked
}
}
})
.clicked {
transform: rotate(360deg);
transition: transform 0.5s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>
<div id="app">
<img
src="https://via.placeholder.com/100/09f.png/fff"
#click="rotation"
:class="{ clicked }"
alt="refresh-icon-btn"
/>
</div>
This could be one of the approach, have a inline style to toggle between 0 & 360 deg and have a constant class for transition.
var vm = new Vue({
el: '#app',
data: {
deg: 0,
},
methods: {
rotation() {
this.deg += 360;
}
}
})
.transition {
transition: transform 0.5s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.min.js"></script>
<div id="app">
<img src="https://via.placeholder.com/100/09f.png/fff" #click="rotation" class="transition" v-bind:style="{transform: `rotate(${deg}deg)`}" alt="refresh-icon-btn" />
</div>
Another approach is to use setTimeout to remove the class after the animation
var vm = new Vue({
el: '#app',
data: {
clicked: false,
rotationDuration: 500
},
methods: {
rotation() {
this.clicked = !this.clicked
setTimeout(() => this.clicked = !this.clicked, this.rotationDuration)
}
}
})
.clicked {
transform: rotate(360deg);
transition: transform var(--rotation-duration) ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>
<div id="app">
<img
src="https://via.placeholder.com/100/09f.png/fff"
#click="rotation"
:class="{ clicked }"
:style="`--rotation-duration: ${rotationDuration}ms`"
alt="refresh-icon-btn"
/>
</div>
You can just use a class binding to toggle the class on and off.
var vm = new Vue({
el: '#app',
data: {
isClicked: false,
},
methods: {
rotation() {
this.isClicked = !this.isClicked
}
}
})
.image {
transition: transform 0.5s ease-in-out;
}
.clicked {
transform: rotate(360deg);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>
<div id="app">
<img
src="https://via.placeholder.com/100/09f.png/fff"
#click="rotation"
class="image"
:class="{ clicked: isClicked }"
alt="refresh-icon-btn"
/>
</div>
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>
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
I have several elements that are displayed as <li> elements in a loop. For each element I want behavior such that when the element is clicked a modal box opens up. Inside the modal box I want contents that are specific to the element that was clicked.
The data below shows all the elements:
{value: 10, name: "foo"},
{value: 12, name: "bar"},
{value: 14, name: "foobar"},
{value: 22, name: "test"},
{value: 1, name: "testtooo"},
{value: 8, name: "something"}
When I click on an element, I want to see the value property for it inside the modal box.
I've created a fiddle for this: https://jsfiddle.net/hvb9hvog/14/
Question
I've gotten the modal working, however, how can I show each elements value property inside the modal?
I am sure there are multiple ways to go about this, but one way would be to create a new data property, let's call it value. When you #click the li you get it's value, set it to the value property and display that value property in the body of the modal ({{this.value}}).
You can have two #click methods, so create another one that updates the data property you just created, called value.
Here's a fiddle
Relevant code changes:
In your li element:
<li v-for="request in filteredRequests">
{{request.name}}
</li>
In your modal markup:
<modal v-if="showModal" #close="showModal = false">
<!--
you can use custom content here to overwrite
default content
-->
<h3 slot="header">custom header</h3>
<div slot="body">
{{this.value}}
</div>
</modal>
In vue data:
data: {
requests: [
{value: 10, name: "foo"},
{value: 12, name: "bar"},
{value: 14, name: "foobar"},
{value: 22, name: "test"},
{value: 1, name: "testtooo"},
{value: 8, name: "something"}
],
num: 0,
showModal: false,
value: 9999999999
},
In vue methods:
methods: {
setVal(val) {
this.value = val;
}
},
Vue.component('modal', {
template: '#modal-template'
})
var vm = new Vue({
el: "#app",
data: {
requests: [{
value: 10,
name: "foo"
},
{
value: 12,
name: "bar"
},
{
value: 14,
name: "foobar"
},
{
value: 22,
name: "test"
},
{
value: 1,
name: "testtooo"
},
{
value: 8,
name: "something"
}
],
num: 0,
showModal: false,
value: 9999999999
},
methods: {
setVal(val) {
this.value = val;
}
},
computed: {
c: function() {
return `Slider Number: (${this.num})`
},
filteredRequests() {
return this.requests.filter(r => r.value > this.num)
}
},
});
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .5);
display: table;
transition: opacity .3s ease;
}
.modal-wrapper {
display: table-cell;
vertical-align: middle;
}
.modal-container {
width: 300px;
margin: 0px auto;
padding: 20px 30px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
transition: all .3s ease;
font-family: Helvetica, Arial, sans-serif;
}
.modal-header h3 {
margin-top: 0;
color: #42b983;
}
.modal-body {
margin: 20px 0;
}
.modal-default-button {
float: right;
}
/*
* The following styles are auto-applied to elements with
* transition="modal" when their visibility is toggled
* by Vue.js.
*
* You can easily play with the modal transition by editing
* these styles.
*/
.modal-enter {
opacity: 0;
}
.modal-leave-active {
opacity: 0;
}
.modal-enter .modal-container,
.modal-leave-active .modal-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://unpkg.com/vue#2.3.4/dist/vue.js"></script>
<script type="text/x-template" id="modal-template">
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
default header
</slot>
</div>
<div class="modal-body">
<slot name="body">
default body
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
default footer
<button class="modal-default-button" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</script>
<div id="app">
<div class="form-horizontal">
<div class="form-group">
<label class="col-md-2 control-label">色</label>
<div class="col-md-10">
<input class="form-control" v-model="c" :style="{backgroundColor: c}" />
<div class="help-block">
<input type="range" min="0" max="360" v-model.number="num" />
<ul class="ml-thumbs">
<li v-for="request in filteredRequests">
{{request.name}}
</li>
</ul>
<modal v-if="showModal" #close="showModal = false">
<!--
you can use custom content here to overwrite
default content
-->
<h3 slot="header">custom header</h3>
<div slot="body">
{{this.value}}
</div>
</modal>
</div>
</div>
</div>
</div>
</div>
Add "req" property to data
data() {
return {
...
req: {},
...
}
}
set click event:
{{request.name}}
add body slot
...
<h3 slot="header">custom header</h3>
<div slot="body">
{{req.value}}
</div>
...
https://jsfiddle.net/w4e6hr86/
I'm not sure if you are asking this using Vue.js or just JS. So, here are my answers (basic examples). I recommend you to read about event delegation + events on vuejs.
Vue Js
<template>
<div class="content">
<ul>
<li v-for="item in items" #click.prevent="showModal(item)">{{ item }}</li>
</ul>
<div class="modal" v-show="isModalVisible">
{{ JSON.stringify(selectedItem) }}
close modal
</div>
</div>
</template>
<script>
export default {
name: 'something',
data () {
return {
selectedItem: item,
items: [{
id: 1,
name: 'something'
}, {
id: 2,
name: 'something #2'
}]
}
},
computed: {
isModalVisible () {
return this.selectedItem !== null
}
}
methods: {
showModal (item) {
this.selectedItem = item
}
}
}
</script>
Plain javascript
const toggleModal = content => {
const $body = document.querySelector('body')
const $modal = $body.querySelector('.modal')
$modal && $modal.remove()
$body.insertAdjacentHTML('beforeend',`<div class="modal">${content}</div>`)
}
document.querySelector('ul').addEventListener('click', e => {
if (! e.target.matches('li')) {
return
}
toggleModal(e.target.innerText)
});
About Event delegation.
About insertAdjacentHtml.
About Vuejs Event handling
I need to add "expand/collapse all" functionality for my Vue component(some collapsible panel).
If user clicks collapse button then clicks on some panel and expand it then clicking on collapse button will do nothing because watched parameter will not change.
So how to implement this functionality properly (buttons must collapse and expand components always)?
I prepared simple example(sorry for bad formatting, it looks nice in editor :( ):
var collapsible = {
template: "#collapsible",
props: ["collapseAll"],
data: function () {
return {
collapsed: true
}
},
watch: {
collapseAll: function(value) {
this.collapsed = value
}
}
}
var app = new Vue({
template: "#app",
el: "#foo",
data: {
collapseAll: true
},
components: {
collapsible: collapsible
}
});
.wrapper {
width: 100%;
}
.wrapper + .wrapper {
margin-top: 10px;
}
.header {
height: 20px;
width: 100%;
background: #ccc;
}
.collapsible {
height: 100px;
width: 100%;
background: #aaa;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
<div id="foo"></div>
<script type="text/x-template" id="collapsible">
<div class="wrapper">
<div class="header" v-on:click="collapsed = !collapsed"></div>
<div class="collapsible" v-show="!collapsed"></div>
</div>
</script>
<script type="text/x-template" id="app">
<div>
<button v-on:click="collapseAll = true">Collapse All</button>
<button v-on:click="collapseAll = false">Expand All</button>
<collapsible v-for="a in 10" v-bind:collapseAll="collapseAll" v-bind:key="a"></collapsible>
</div>
</script>
Thanks!
This is a case where I might use a ref.
<button v-on:click="collapseAll">Collapse All</button>
<button v-on:click="expandAll">Expand All</button>
<collapsible ref="collapsible" v-for="a in 10" v-bind:key="a"></collapsible>
And add methods to your Vue.
var app = new Vue({
template: "#app",
el: "#foo",
methods:{
collapseAll(){
this.$refs.collapsible.map(c => c.collapsed = true)
},
expandAll(){
this.$refs.collapsible.map(c => c.collapsed = false)
}
},
components: {
collapsible: collapsible
}
});
Example.