Button doesn't call the function for the first click - vue.js

I have a checkbox and a button that when it's checked, then, the button suppose to submit, but when it's unchecked, the button is disable.
However, my button keep disabling and I can't submit, the reason because I add: v-on:click.prevent="checkboxJsInput"
The reason I use "prevent" is because I have disable and enable button, and "prevent" is to disable the button from submit when it's disable.
If I don't use "prevent", the button would submit even it's disable and the strange thing it's enable for the first time, but after you "check" for the second and so on, it's working.
My question, how to make the button works?
so when the button is grey, it's supposed to disable, when it's blue, it's supposed to enable to submit.
Is there a way to do this in vue way rather than using native javascript in vue?
checkboxJsInput: function() {
const checkBox = document.querySelector(".guestlistCheckboxJs");
const buttonFormJs = document.querySelector(".buttonCheckboxJs");
if (checkBox != null) {
checkBox.addEventListener("click", function() {
if (this.checked) {
buttonFormJs.disabled = false;
buttonFormJs.classList.remove("disabled");
} else {
buttonFormJs.disabled = true;
buttonFormJs.classList.add("disabled");
}
});
}
}
}
.buttonCheckboxJs.disabled {
background-color: #d6d6d6;
}
.guestlist-form-wrapper-checkbox {
display: flex;
margin: 20px 0 30px 0;
}
.guestlist-text-checkbox {
color: #ffffff;
margin: 5px 10px;
}
.guestlist-input-checkbox[type="checkbox"] {
display: none;
width: 45px;
height: 45px;
background: #d6d6d6;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05),
inset 0 -15px 10px -12px rgba(0, 0, 0, 0.05);
}
.guestlist-span-checkbox {
visibility: visible;
content: "";
display: block;
width: 25px;
height: 25px;
font-size: 20px;
text-align: center;
color: #ffffff;
background-color: #4990e2;
display: flex;
justify-content: center;
align-items: center;
border-radius: 3px;
}
.guestlist-input-checkbox[type="checkbox"]:checked
+ label
.guestlist-span-checkbox::before {
content: "✔";
}
.guestlist-form-wrapper-textarea {
width: 100%;
margin-top: 25px;
}
.guestlist-textarea {
background-color: transparent;
text-indent: 5px;
height: 50px;
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
color: #ffffff;
}
.button-style {
background-color: #4990e2;
border: 0;
color: #ffffff;
font-weight: bold;
font-size: 18px;
padding: 15px 20px;
border-radius: 5px;
width: 600px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="guestlist-form-wrapper-checkbox">
<input
class="guestlist-input-checkbox guestlistCheckboxJs"
v-on:click="checkboxJsInput"
type="checkbox"
value="1"
name="OptIn"
id="OptIn"
>
<label class="guestlist-label-checkbox" for="OptIn">
<span class="guestlist-span-checkbox"></span>
</label>
</div>
<button
v-on:click.prevent="checkboxJsInput"
class="button-style buttonCheckboxJs disabled"
>Check Availability</button>

First of all I don't understand why you are using an event listner inside an event listner for that checkbox click.
Secondly, you can use v-model.
In your data, add property check like this:
data() {
return {
check: false
}
}
Then in your html use like this:
<div class="guestlist-form-wrapper-checkbox">
<input
class="guestlist-input-checkbox guestlistCheckboxJs"
v-model="check"
type="checkbox"
value="1"
name="OptIn"
id="OptIn"
>
<label class="guestlist-label-checkbox" for="OptIn">
<span class="guestlist-span-checkbox"></span>
</label>
</div>
<button
:disabled="check"
class="button-style buttonCheckboxJs disabled"
>Check Availability</button>
I just use a check property which is a boolean. Then you can run your function on click of that button.

Consider using v-model for this.
https://v2.vuejs.org/v2/guide/forms.html#v-model-with-Components
Your checkbox can have its v-model bound to a data property, and the disabled property on the button can be bound to the same. It'd look like this:
<template>
<input type="checkbox" v-model="enableSubmit" />
<button #click="mySubmitFunction()" :disabled="!enableSubmit">
</template>
<script>
export default {
data: function() {
return {
enableSubmit: false
}
},
methods: {
mySubmitFunction() {
//...
}
}
}
</script>

Related

Animate width of div bound to data property in Vue

I have this progress bar div, whichs width is bound to the data property result and changes accordingly. At the moment it still jumps, but I want to animate it. I thought of tracking the old and the new Value and injecting it in the css with css variables or just using a setInterval method, but tracking the 2 values seems to get quite complicated and it seemed like a overkill for me. Does anyone have an easier idea?
<template>
<div class="progress">
<div class="progress-value" :style="{ 'width': result + '%' }">
<h2>{{ result }}%</h2>
</div>
</div>
</template>
<script>
export default {
props: ["result"],
};
</script>
<style scoped>
.progress {
background: #ccc;
border-radius: 100px;
position: relative;
padding: 5px 5px;
margin: 5px 5px;
height: 40px;
width: auto;
}
.progress-value {
animation: load 3s normal forwards;
border-radius: 100px;
background: #fff;
height: 30px;
text-align: center;
}
/* #keyframes load {
0% {
width:
}
100% {
width:
}
} */
</style>
Add css transition like this:
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 1s;
And fix the binding:
<div class="progress-value" :style="'width: ' + result + '%'">
See this example
<template>
<div class="progress">
<div class="progress-value" :style="'width: ' + result + '%'">
<h2>{{ result }}%</h2>
</div>
</div>
</template>
<script>
export default {
data () {
return {
result: 5
}
},
mounted() {
this.increment()
},
methods: {
increment() {
this.result += 10
if (this.result < 95) {
setTimeout(this.increment, 1000)
}
}
}
}
</script>
<style scoped>
.progress {
background: #ccc;
border-radius: 100px;
position: relative;
padding: 5px 5px;
margin: 5px 5px;
height: 40px;
width: auto;
}
.progress-value {
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 1s;
background: #fff;
height: 30px;
text-align: center;
}
</style>

v-show or v-if div shifted the others div into the parent container

I would like to add dynamically image (check arrow) into a div which contains other div information (image + texte).
This adding means that users owned this "object" in the application. He can added or deselected this object on the fly.
The result of this action is appear or disappear arrow on the fly.
This process run perfectly in my vue.js component. But I can view appear an offset each time that arrow appears like this :
I try to use v-if or v-show vue.js directives but the result is the same.
I don't want to have a new offset appear when action to checked item is launch.
this is the HTML template :
<div id="container">
<div #click="checkedItem(key.id)" class="container-item" v-for="(key, value) in objects">
<div class="check" v-show="checked[key.id]">
<img height="35" width="35" src="../../../static/checked.png"></img>
</div>
<div class="image">
<img src="https://picsum.photos/75/75/?image=25" alt="logo"></img>
</div>
<div class="name">{{ key.name }}</div>
</div>
</div>
This is the CSS stylesheet component :
<style scoped>
#container
{
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.container-item
{
display: flex;
flex-direction: row;
margin: 30px 30px 30px 30px;
}
.container-item div.check
{
z-index: 3;
position: relative;
left: 20px;
bottom: 15px;
}
.container-item div.check > img
{
height: 35px;
width: 35px;
}
.container-item div.image
{
z-index: 2;
border: 1px solid blue;
}
.container-item div.name
{
z-index: 1;
position: relative;
top: 12px;
right: 10px;
text-align: center;
line-height: 45px;
width: 150px;
max-height: 50px;
border: 1px solid blue;
background-color: yellow;
}
</style>
And the checkedItem method component :
checkedItem: function (id)
{
let isChecked = this.checked[id] ? true : false
isChecked ? this.checked[id] = false : this.checked[id] = true
},
How can "add" DOM node HTML into HTML template component vue.js whitout create a new offset on the fly ?
As we have discussed in the comments, this is purely a CSS thing. What you want is to take div.check out from the layout flow, so that it does not influence the positioning of other elements. This can be done by adding position: absolute to its ruleset. For that to work, remember to add position: relative to its parent, too:
.container-item {
display: flex;
flex-direction: row;
margin: 30px 30px 30px 30px;
/* Allow div.check to be positioned relative to its parent */
position: relative;
}
.container-item div.check {
z-index: 3;
left: 20px;
bottom: 15px;
/* Take the check icon out of layout flow */
position: absolute;
}

How to get VueJS transitioning Divs beside eachother?

When using Vue transitions with a slide left/right animation, how can I get the Divs beside eachother?
Take a look at this pen;
https://codepen.io/anon/pen/jeBBaB
HTML
<div class="heading">
<h1>Transition demo</h1>
<h4>Why this no work?</h4>
</div>
<div class="container" id="app">
<transition :enter-active-class="enterAnimation" :leave-active-class="leaveAnimation" mode="">
<div key="one" v-if="currentStep == 1">
This is Step One
<button class="btn btn-primary" #click="currentStep = 2; previousStep=1">Next</button>
</div>
<div key="two" v-else>
This is Step Two
<button class="btn btn-primary" #click="currentStep = 1; previousStep=2">Back</button>
</div>
</transition>
</div>
CSS
$purple: #5c4084;
body {
background-color: $purple;
padding: 50px;
}
.container {
padding: 40px 80px 15px 80px;
background-color: #fff;
border-radius: 8px;
max-width: 800px;
}
.heading {
text-align: center;
h1 {
background: -webkit-linear-gradient(#fff, #999);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
text-align: center;
margin: 0 0 5px 0;
font-weight: 900;
font-size: 4rem;
color: #fff;
}
h4 {
color: lighten(#5c3d86,30%);
text-align: center;
margin: 0 0 35px 0;
font-weight: 400;
font-size: 24px;
}
}
.btn{
outline: none !important;
}
.btn.btn-primary {
background-color: $purple;
border-color: $purple;
outline: none;
&:hover {
background-color: darken($purple, 10%);
border-color: darken($purple, 10%);
}
&:active, &:focus {
background-color: lighten($purple, 5%);
border-color: lighten($purple, 5%);
}
& .fa {
padding-right: 4px;
}
}
.form-group {
margin-bottom: 25px;
}
JS
new Vue({
el: '#app',
data: {
currentStep: 1,
previousStep: 0
},
computed:{
enterAnimation() {
if (this.currentStep < this.previousStep) {
return "animated slower fadeInLeft";
} else {
return "animated slower fadeInRight";
}
},
leaveAnimation() {
if (this.currentStep > this.previousStep) {
return "animated slower fadeOutLeft";
} else {
return "animated slower fadeOutRight";
}
}
}
});
When using no mode, the "entering" div appears on the line below the "leaving" div until then end, then it pops upwards.
I can use mode="out-in" but then there's a noticable gap between the Divs. I'd like to just have one sliding in, right next to the one sliding out. Any way to achieve this?
You could make use of absolute positioning on the div elements, you need to adjust your css a bit though.
But as a starting point, change your .container rules to this (adding position: relative;):
.container {
padding: 40px 80px 15px 80px;
background-color: #fff;
border-radius: 8px;
max-width: 800px;
position:relative;
}
and add this as a new rule below it:
.container div {position:absolute;top:0;left:0;}
The flexbox way:
Change your container rules to this:
.container {
padding: 40px 80px 15px 80px;
background-color: #fff;
border-radius: 8px;
max-width: 800px;
display:flex;
}
After this you can use a css translate rule to position the content. You can see a working example here:
Vue transitions

Apply style to an HTML element added dynamically by v-for using Vuejs

I'm adding elements to a list dynamically using v-for.
<ol>
<li v-for="light in lights">
<input type="range" min="0" max="255" v-model="light.currentBrightness" v-on:change="setBrightness(light)" />
</li>
</ol>
I want to decorate the slider using rangeslider.
Problem is, when a new element is added after the DOM is initialized, it's not taking the style specified in rangeslider.js. Way to fix this is to call the reinitialize method in rangeslider.js which will redecorate all the slider elements.
I'm not sure how to call the javascript method when the element is added dynamically during the runtime. Does anyone how to do it? To me, it seems like a very common problem but I could not find a solution by Googling.
My issue is same as discussed in github.
If you're new to JavaScript and Vue, you're diving in pretty close to the deep end. The rangeslider isn't just styling (like CSS), it's a widget that replaces the built-in range input.
One basic idea behind Vue is that it controls the DOM and you only modify your model, but there are some carefully controlled exceptions. Components have lifecycle hooks where you are allowed to insert and modify DOM elements owned by the component.
Some instructions for v-model support:
So for a component to work with v-model, it should (these can be
configured in 2.2.0+):
accept a value prop
emit an input event with the new value
So we make a component whose template is a range input element. We give it a value prop. In the mounted hook, we initialize the rangeslider on the input element (made available as el), then set it up to emit input events on change.
new Vue({
el: '#app',
data: {
lights: [{
currentBrightness: 10
},
{
currentBrightness: 30
}
]
},
methods: {
addRange: function() {
this.lights.push({
currentBrightness: 50
});
}
},
components: {
rangeSlider: {
props: ['value', 'min', 'max'],
template: '<input min="{{min}}" max="{{max}}" type=range />',
mounted: function() {
var vm = this
$(this.$el)
.val(this.value)
// init rangeslider
.rangeslider({
polyfill: false
})
// emit event on change.
.on('change', function() {
vm.$emit('input', this.value)
})
}
}
}
});
<link href="//cdnjs.cloudflare.com/ajax/libs/rangeslider.js/2.3.0/rangeslider.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.2/vue.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/rangeslider.js/2.3.0/rangeslider.min.js"></script>
<div id="app">
<ol>
<li v-for="light in lights">
<range-slider v-model="light.currentBrightness" min="0" max="255"></range-slider>
<div>{{light.currentBrightness}}</div>
</li>
</ol>
<button #click="addRange">Add Range</button>
</div>
You can use the below CSS codes to apply some stylings in the html5 range input:
body {
padding: 30px;
}
input[type=range] {
/*removes default webkit styles*/
-webkit-appearance: none;
/*fix for FF unable to apply focus style bug */
border: 1px solid white;
/*required for proper track sizing in FF*/
width: 300px;
}
input[type=range]::-webkit-slider-runnable-track {
width: 300px;
height: 5px;
background: #ddd;
border: none;
border-radius: 3px;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
border: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: goldenrod;
margin-top: -4px;
}
input[type=range]:focus {
outline: none;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #ccc;
}
input[type=range]::-moz-range-track {
width: 300px;
height: 5px;
background: #ddd;
border: none;
border-radius: 3px;
}
input[type=range]::-moz-range-thumb {
border: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: goldenrod;
}
/*hide the outline behind the border*/
input[type=range]:-moz-focusring{
outline: 1px solid white;
outline-offset: -1px;
}
input[type=range]::-ms-track {
width: 300px;
height: 5px;
/*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead */
background: transparent;
/*leave room for the larger thumb to overflow with a transparent border */
border-color: transparent;
border-width: 6px 0;
/*remove default tick marks*/
color: transparent;
}
input[type=range]::-ms-fill-lower {
background: #777;
border-radius: 10px;
}
input[type=range]::-ms-fill-upper {
background: #ddd;
border-radius: 10px;
}
input[type=range]::-ms-thumb {
border: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: goldenrod;
}
input[type=range]:focus::-ms-fill-lower {
background: #888;
}
input[type=range]:focus::-ms-fill-upper {
background: #ccc;
}

CSS TranslationX of container not working in Safari

Today I have noticed a weird behavior of Safari (9.0) when I applied a transition to an element that was translating on the X axis while the width was also increasing.
I have reproduced the behavior in this JsFiddle. Here is an embed code for those who like it better. In Firefox and Chrome it looks pretty smooth but not in Safari, does anyone have a solution or a best way to achieve the same effect?
var button = document.getElementsByTagName('button')[0],
container = document.getElementsByTagName('div')[0];
button.addEventListener('click', function() { container.classList.toggle('open'); });
.container {
width: 100%;
overflow: hidden;
}
ul {
display: block;
width: 100%;
padding: 0;
margin: 0;
transition: width 1s, transform 1s;
}
.open ul {
width: 200%;
transform: translateX(-50%);
}
li {
/* Just some style first */
font-family: sans-serif;
color: #fff;
text-align: center;
text-transform: uppercase;
background-color: red;
padding: 1em 0;
display: inline-block;
width: calc(50% - 4px);
}
li:first-child {
background-color: green;
}
<div class="container">
<ul>
<li>Test</li>
<li>Test</li>
</ul>
</div>
<button type="button">Toggle translation</button>
Re-posting as an answer.
Here is the jsFiddle result and snippet as below:
var button = document.getElementsByTagName('button')[0];
var container = document.getElementsByTagName('div')[0];
var timeline = new TimelineMax({ paused: true });
timeline.to('ul', 1, { width: '200%', xPercent: -50, ease: Power2.easeInOut });
button.addEventListener('click', function() {
timeline.progress() > 0 ? timeline.reverse() : timeline.play();
});
.container {
width: 100%;
overflow: hidden;
}
ul {
display: block;
width: 100%;
padding: 0;
margin: 0;
}
li {
font-family: sans-serif;
color: #fff;
text-align: center;
text-transform: uppercase;
background-color: red;
display: inline-block;
padding: 1em 0;
width: calc(50% - 4px);
}
li:first-child {
background-color: green;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.18.2/TweenMax.min.js"></script>
<div class="container">
<ul>
<li>Test</li>
<li>Test</li>
</ul>
</div>
<button type="button">Toggle translation</button>
Hope this is helpful.
P.S. I have been using GSAP for quite a while now and I don't remember getting stuck on any browser-specific issues unless a browser would do something differently. A little research into GSAP and it would tell you that browser compatibility is one of their main selling points.
By animating margin-left instead of translateX the result is acceptable in Safari:
var button = document.getElementsByTagName('button')[0],
container = document.getElementsByTagName('div')[0];
button.addEventListener('click', function() {
container.classList.toggle('open');
});
.container {
width: 100%;
overflow: hidden;
}
ul {
display: block;
width: 100%;
padding: 0;
margin: 0;
transition: width 1s, margin-left 1s;
}
.open ul {
width: 200%;
margin-left:-100%;
}
li {
font-family: sans-serif;
color: #fff;
text-align: center;
text-transform: uppercase;
background-color: red;
display: inline-block;
padding: 1em 0;
width: calc(50% - 4px);
}
li:first-child {
background-color: green;
}
<div class="container">
<ul>
<li>Test</li>
<li>Test</li>
</ul>
</div>
<button type="button">Toggle translation</button>
Using scaleX instead of animating width is smoother, but probably not what you want.
var button = document.getElementsByTagName('button')[0],
container = document.getElementsByTagName('div')[0];
button.addEventListener('click', function() {
container.classList.toggle('open');
});
.container {
width: 100%;
overflow: hidden;
}
ul {
display: block;
width: 100%;
padding: 0;
margin: 0;
transition: transform 1s;
}
.open ul {
transform: translateX(-50%) scaleX(2);
}
li {
font-family: sans-serif;
color: #fff;
text-align: center;
text-transform: uppercase;
background-color: red;
display: inline-block;
padding: 1em 0;
width: calc(50% - 4px);
}
li:first-child {
background-color: green;
}
<div class="container">
<ul>
<li>Test</li>
<li>Test</li>
</ul>
</div>
<button type="button">Toggle translation</button>
So, I will try to sum up the two best solutions here : one with CSS transform and the other with Javascript animation (GSAP).
CSS TRANSFORM
In terms of performance, it is recommended to only animate transforms (translate, scale, rotate) and opacity. If you are interested in more optimisation details you can have a look at this article by Anna Migas.
So, as #Meiko suggested, the best solution is to only animate scale and translate properties. Here is a code sample (and the JSFiddle)
var button = document.getElementsByTagName('button')[0],
container = document.getElementsByTagName('div')[0];
button.addEventListener('click', function() {
container.classList.toggle('open');
})
.container,
ul {
width: 100%;
}
ul {
overflow: hidden;
/* reset default browser styles */
padding: 0;
margin: 0;
}
li {
display: inline-block;
width: calc(50% - 2px);
position: relative;
transition: transform 1s;
/* Just some style */
font-family: sans-serif;
color: #fff;
text-align: center;
text-transform: uppercase;
padding: 1em 0;
}
li::before {
content: '';
position: absolute;
top: 0;
left: 0;
display: block;
width: 100%;
height: 100%;
background-color: red;
z-index: -1;
transition: transform 1s;
}
li:first-child::before {
background-color: green;
}
.open li:first-child {
transform: translateX(-100%);
}
.open li:nth-of-type(2) {
transform: translateX(-50%);
}
.open li:nth-of-type(2)::before {
transform: scaleX(2);
}
<div class="container">
<ul>
<li>Test</li>
<li>Test</li>
</ul>
</div>
<button type="button">Toggle translation</button>
PROS:
Only use a tiny bit of Javascript to toggle class,
The browser support is quite good (needs vendor-specific properties and some testing),
Really fast and light on GPU memory.
CONS:
Pretty limited in terms of usage (the actual width of the second cell stays the same),
Needs more lines of CSS.
JS ANIMATION (WITH GSAP)
This solution has been suggested by #Tahir Ahmed and use the GSAP library. As a side note, I really think that this is the best js library out there for this kind of animation. Here is a snippet of how it works (and the JSFiddle):
var button = document.getElementsByTagName('button')[0],
timeline = new TimelineMax({ paused: true });
timeline.to('ul', 1, { width: '200%', xPercent: -50 });
button.addEventListener('click', function() {
timeline.progress() > 0 ? timeline.reverse() : timeline.play();
})
.container {
width: 100%;
overflow: hidden;
}
ul {
width: 100%;
/* reset default browser styles */
padding: 0;
margin: 0;
}
li {
display: inline-block;
width: calc(50% - 2px);
background-color: red;
/* Just some style */
font-family: sans-serif;
color: #fff;
text-align: center;
text-transform: uppercase;
padding: 1em 0;
}
li:first-child {
background-color: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.2/TweenMax.min.js"></script>
<div class="container">
<ul>
<li>Test</li>
<li>Test</li>
</ul>
</div>
<button type="button">Toggle translation</button>
PROS:
Really flexible, sky is the limit!
You can animate properties such as display (you can't in CSS),
Compatible with every browser out there (down to IE6).
CONS:
Require a third party library (about 30kb),
Seems a bit harder for the GPU (although it needs more testing to be sure).
In the end it really depends on the animation you need but if it get's a little bit more complex than moving a container around then I will choose GSAP.