I my app UI I have a table with a set of permissions listed. In each row there is a toggle-button that sets the default state of each permission to either "deny" or "grant" in the DB.
If the user clicks the button, async action is triggered in the background. It all works perfectly fine, but what I want to add is when user click the button its inner html changes to a spinner or some sort of "wait..." text and the button get disable while the action runs. This is to prevent user from clicking multiple time is the action take a bit longer to complete, giving impression like nothing is happening.
Now, I know how to do it in jQuery or even plain JS, but I have no idea how to access the button properties in VUE.js
My button look like this:
<button #click="defaultPermissionState(perm.id,'grant',$event)">Deny</button>
I'm only recently started into vue.js, so still learning it ;)
UPDATE: I've actually managed to find a way to do it by exploring the $event and being able to change the text and button properties by doing this:
event.path[0].innerHTML = 'wait...';
event.path[0].disabled = true;
but this does not look like a very elegant solution, so if anyone knows of something better I would still like to hear it
You can use v-if with :disabled. Check this quick example:
new Vue({
el: "#app",
data: {
isLoadingArray: []
},
methods: {
clicked(index) {
this.$set(this.isLoadingArray, index, true)
setTimeout(() => {
this.$set(this.isLoadingArray, index, false)
}, 2000)
}
}
})
.lds-dual-ring {
display: inline-block;
width: 64px;
height: 64px;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 46px;
height: 46px;
margin: 1px;
border-radius: 50%;
border: 5px solid #fff;
border-color: #fff transparent #fff transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
#keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button type="button" #click="clicked(0)" :disabled="isLoadingArray[0]">
<div v-if="isLoadingArray[0]" class="lds-dual-ring"></div>
<span v-else>click me</span>
</button>
<button type="button" #click="clicked(1)" :disabled="isLoadingArray[1]">
<div v-if="isLoadingArray[1]" class="lds-dual-ring"></div>
<span v-else>click me</span>
</button>
<button type="button" #click="clicked(2)" :disabled="isLoadingArray[2]">
<div v-if="isLoadingArray[2]" class="lds-dual-ring"></div>
<span v-else>click me</span>
</button>
</div>
You can do it like this
data: function() {
return {
waiting: false,
...otherstuffs
}
},
methods: {
callAsync() {
this.waiting = true;
callASYNC()
.then((result) => {
this.waiting = false;
})
}
}
In your HTML
<button :disabled="waiting"> {{ waiting ? 'Waiting ...' : 'Deny' }} </button>
So basically, just set a flag before you hit the request, and set it back when the call finishes. Use this flag to set the button value to whatever you want
This should help
<template>
<button disabled={{disableBtn}}
#click="defaultPermissionState(perm.id,'grant',$event)">{{btnText}}
</button>
</template>
export default {
data() {
return {
btnText: 'Deny',
disableBtn: false
}
},
method: {
defaultPermissionState(id, type, e) {
this.disableBtn = true;
this.btnText = 'Clicking.....';
}
}
}
Hide the button and show the spinner using a data or computed property. Update the 'busy' property from your async function.
<button v-if='!busy' #click="defaultPermissionState(perm.id,'grant',$event)">Deny</button>
<spinner v-else />
you can use $event to change the inner html for buttons
$event.path[0].innerHTML = "Write the inner html"
Related
May be somebody can help me.
I have the popup and button, when I click to create button the popup window has to open and when I click outside or on button the popup has to hide
I implement the example in sandbox (unfortunately it works locally but doesn't work in sandbox, I can't understand the reason of problem in sandbox, code base the same, I hope the example will be useful)
I implement directive:
export default {
name: 'click-outside',
mounted: function (el, binding, vnode) {
el.clickOutsideEvent = function (event) {
event.stopPropagation();
if (!(el === event.target || el.contains(event.target))) {
binding.value;
}
};
document.addEventListener('click', el.clickOutsideEvent);
},
unmounted: function (el) {
document.removeEventListener('click', el.clickOutsideEvent);
},
};
simple make up:
<template>
<div class="component">
<button ref="button" #click="isDisplay = true">Create</button>
<div v-if="isDisplay" class="popup-box" v-click-outside="onClose">
Test Popup Box
</div>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
isDisplay: true,
};
},
method: {
onClose() {
this.isDisplay = false;
},
},
};
</script>
<style scoped>
.component {
display: flex;
justify-content: column;
}
.popup-box {
position: absolute;
left: 50%;
top: 50px;
transform: translateX(-50%);
background: #f0f8ff;
border-radius: 1px;
box-shadow: 1px 1px 15px 0 rgba(0, 0, 0, 0.2);
padding: 40px;
color: #555585;
}
</style>
and make global the connection for directive:
import { createApp } from 'vue';
import App from './App.vue';
import ClickOutside from './directives/clickOutside.js';
const app = createApp(App);
app.directive('click-outside', ClickOutside);
app.mount('#app');
in the result Yes it works....
when I am clicking the create button 'isDisplay' set firstly as true (click event) then false (directive) and it is problem which I don't know how to fix, I tried to use ref for button but I don't clearly understand how to ingore the ref attribute in directive ( I didn't find any place where I can check ref and current click with, to understand in which place click event is triggered)
With your code base you need to assign v-click-outside on your component, or on the inner wrapper
<template>
<div class="component" v-click-outside="onClose">
<button ref="button" #click="isDisplay = true">Create</button>
<div v-if="isDisplay" class="popup-box">
Test Popup Box
</div>
</div>
</template>
And in your code you have a misspell in method field. You need to methods instead
Hello everyone~ At present, I have just come into contact with vue. There is a project that needs to make the input box higher after clicking the button, but I have difficulty writing the effect!
I would like to ask everyone to help me see if there is a mistake in the code. ? Thank you all for your help!
HTML
<div id="myApp">
<textarea class="message" placeholder="pleace enter you message" :class="expand_message"></textarea>
click
</div>
CSS
.message{
display: block;
}
.expand_message{
height: 300px;
}
JavaScript
let myApp = new Vue({
el:"#myApp",
data:{
expand_message:true
},
methods:{
btnClick: function(){
// 點擊變紅色與回復原狀
this.expand_message = !this.expand_message;
},
},
});
My program template
so first off try to keep code on stackoverflow. e.g. in your question.
<div id="myApp">
<textarea placeholder="pleace enter you message" :class="['message', expand_message ? 'expand_message' : null]"></textarea>
<button #click="btnClick">click</button>
</div>
And to get your animation, you can not transition on height however you can transition on max height:
.message{
display: block;
max-height: 40px;
height: 300px;
transition: max-height ease-out 1s;
}
.expand_message{
max-height: 300px;
}
Try this:
:class="{'expand_message': expand_message}"
I've been trying to mock a carousel-like effect in cards rendered through v-for. I have an array of data and I have a method that left rotates that array. I'm passing that rotated array in v-for. But, the rotated arrays shift the real dom div instead of re-rendering the component in v-for (I think this is how Vue behaves for optimization). I've tried transition-group but it only applies transition to leaving and entering div. Is there any way so that I can get a carousel-like effect (divs moving upward) using Vue transition? When I was writing the code, the divs were moving upward and behaving as expected because at that time instead of divs shifting in real dom, only the data inside that div were changing but later on it started to behave like this (divs shifting in real dom)
Here is the fiddle link: https://jsfiddle.net/aanish/7pe5jq9u/4/
Please help me to achieve the expected behavior.
var Box = {
props: ['achievement'],
template: '<transition name="slide-up"><div class="box" :key="achievement">{{achievement}}</div></transition>',
};
new Vue({
el: '#app',
data: {
achievements: ['Title1', 'Title2', 'Title3', 'Title4']
},
created() {
this.autoRotateArr();
},
components: {
'box': Box
},
methods: {
leftRotateArr() {
this.achievements.push(
this.achievements.shift()
)},
autoRotateArr() {
this.interval = setInterval(() => this.leftRotateArr(), 3000);
}
}
})
.box-wrapper {
display: flex;
flex-direction: column;
margin: 0 auto;
padding: 50px;
}
.box {
height: 50px;
background-color: orange;
margin: 10px 0;
}
.slide-up-enter-active {
transition: all 666ms cubic-bezier(0, 0, 1, 1);
}
.slide-up-enter {
transform: translateY(50px);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="hero">
<box :achievement="achievements[0]"></box>
</div>
<div class="box-wrapper">
<div><button #click="leftRotateArr">Up</button></div>
<box v-for="achievement in achievements" :achievement="achievement" :key="achievement"></box>
</div>
</div>
.
From the help of Michal Levý comment above, I get the expected behavior using
transition-group
I am creating a game with HTML, CSS, & JavaScript. I have 5 h2(s) and I want to wait for the animation to finish before moving on to any part of the code. The animation works how I want it to but the JavaScript is starting the next animation before the first one is even done! I have tried using
window.setTimeOut
but no luck. Here is the code with the required code: https://codepen.io/HumanFriend/pen/mdOBvoL
Can someone help me?
You can listen to animationend event, fe:
const animated = document.querySelector('.animated');
animated.addEventListener('animationend', () => {
console.log('Animation ended');
});
Read more: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/animationend_event
Random thing:
When you use setTimeout(nextI_ifelse(), 5000); you just invoke the nextI_ifelse inline (and you're setting timeout for the value returned from this function). Change it to setTimeout(nextI_ifelse, 5000);
Any you just want to read about algorithms in general. What you try to do makes sense, but the way you're trying to achieve that does not. The for loop in your codepen runs instantly, so iLevel is directly set to the last value.
To elaborate on entio's answer, your animation occurs when an element has the class "typing-effect." When that animation ends, the browser calls an event called "animationend." You can use JavaScript to run your animation on the next element by accessing that event.
Notice in HTML the snippit below, I've moved the "display: hidden" to a CSS class and removed the "typing-effect." In my JavaScript function, I enable the class in the first element, increment the counter, and told the "animationend" to call the function again with the new value for "i."
Edit: I forgot to mention, I modified the id values. I don't believe a valid id value in HTML5 can contain parenthesis.
console.log("script.js connected");
let iLevel = 1;
let loop = 0;
function animateNext(i){
let stop = document.querySelectorAll('.animated').length;
let animated = document.querySelector(`#h2_${i}`);
animated.classList.add('typing-effect');
animated.addEventListener('animationend', () => {
if(i===stop){
iLevel = "iDone";
}else{
animateNext(i+1);
}
});
}
function startGame() {
animateNext(1);
}
.animated{
display: none;
}
.typing-effect {
display: block;
animation: typing-effect 5s steps(130, end), 0.75s step-end infinite;
white-space: nowrap;
overflow: hidden;
border-right: 2px solid black;
}
.typing-effect:after {
content: " ";
}
#keyframes typing-effect {
0% {
width: 0%;
}
100% {
width: 100%;
}
}
#keyframes blink-caret {
from,
to {
border-color: transparent;
}
50% {
border-color: black;
}
}
<button onclick="startGame();">START GAME</button>
<div id="instructions">
<div>
<h2 id="h2_1" class="animated">Welcome to The Number Wizrd</h2>
</div>
<div>
<h2 id="h2_2" class="animated">Adjfosdf</h2>
</div>
<div>
<h2 id="h2_3" class="animated">sosidjfs</h2>
</div>
<div>
<h2 id="h2_4" class="animated">difjspodf</h2>
</div>
<div>
<h2 id="h2_5" class="animated">skidjfosidf</h2>
</div>
</div>
I am trying to create a Lightbox-like effect with CSS and Javascript. I'm getting an element by it's id (OverlayContainer) and it will change it's classname to accomodate the dark background for now. I have set up the code in a way that it checks classname value of the element (OverlayContainer) to see whether the classname is set to inactive(normal background) or active (darker). However when i press the submit button to change the state it appears to change classes for a second (screen gets darker for a split second) but then reverts back to original state (OverlayInactive). If anyone has any kind of explanation for this happening please respond.
Here is my CSS code:
.OverlayBoxInactive {
background-color: rgba(0, 0, 0, 0);
position: absolute;
width: 0%;
height: 0%;
top: 0px;
left: 0px;
}
.OverlayBoxActive {
background-color: rgba(0, 0, 0, 0.85);
position: absolute;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
}
and here is my Javascript code:
function ActivateOverlay() {
var overlayBox = document.getElementById("OverlayContainer");
var elementClassName = overlayBox.className;
if (elementClassName == "OverlayBoxInactive") {
overlayBox.setAttribute("class", "OverlayBoxActive");
//alert('Overlay Activated');
} else if (elementClassName == "OverlayBoxActive") {
overlayBox.setAttribute("class", "OverlayBoxInactive");
//alert('Overlay Inactivated');
}
}
Thanks in advance,
-Realitiez
EDIT POST: http://jsfiddle.net/bk5e9t0e/
The default action of an input type="submit" is to post the form's data back to the server, which causes your page to reload. This is why your class is removed.
If you wish to prevent the page reload, you need to prevent the default action. The easiest way to do this is to return false from the onclick handler:
onclick="ActivateOverlay(); return false;"
Your code was not working because:
You were using input type=submit [so you were getting a refresh page kind of feel], which should be type=button or return false; in click handler
You were calling function ActivateOverlay which was defined later than the call itself.
Find your solution here in jsFiddle
<script>
function ActivateOverlay() {
//alert('Overlay Activated');
var overlayBox = document.getElementById("OverlayContainer");
var elementClassName = overlayBox.className;
if (elementClassName == "OverlayBoxInactive") {
overlayBox.setAttribute("class", "OverlayBoxActive");
//alert('Overlay Activated');
} else if (elementClassName == "OverlayBoxActive") {
overlayBox.setAttribute("class", "OverlayBoxInactive");
//alert('Overlay Inactivated');
}
}
</script>
<div id="OverlayContainer" class="OverlayBoxInactive"></div>
<div class="main"></div>
<fieldset>
<form>
<input type="button" onclick="ActivateOverlay();return false" value="Hit Me"></input>
</form>
</fieldset>
<script type="text/javascript" src="index.js"></script>
http://jsfiddle.net/4queag8m/
You need to add an preventDefault() in your function. it's reloading the page.
First add the element to your call:
onclick="ActivateOverlay(this)"
and then add the parameter with the prevent to your function - like this"
function ActivateOverlay(evt) {
evt.preventDefault()
...
}