Custom switch component with v-model doesn't work - vue.js

trying to do my custom Switch component in VueJS following these manuals Using v-model on Components, Adding v-model Support to Custom Vue.js Components but something wrong and it doesn't work.
My SwitchTest component:
<template>
<div>
<input type="checkbox" :id="_uid"
:value="value"
#input="updateData"
class="checkbox">
<label :for="_uid" class="switch"></label>
</div>
</template>
<script>
export default {
props: ["value"],
data() {
return {};
},
methods: {
updateData($event) {
console.log($event.target.value);
this.$emit("input", $event.target.value);
}
}
};
</script>
<style scoped>
.switch {
position: relative;
display: inline-block;
width: 40px;
height: 20px;
background-color: rgba(0, 0, 0, 0.25);
border-radius: 20px;
transition: all 0.3s;
}
.switch::after {
content: "";
position: absolute;
width: 18px;
height: 18px;
border-radius: 50%;
background-color: white;
top: 1px;
left: 1px;
transition: all 0.3s;
}
.checkbox:checked + .switch::after {
left: 20px;
}
.checkbox:checked + .switch {
background-color: #7983ff;
}
.checkbox {
display: none;
}
</style>
And using SwitchTest component:
{{switch1}}
<SwitchTest v-model="switch1"/>
{{switch2}}
<SwitchTest v-model="switch2"/>
You can see the whole MVCE example here: https://codesandbox.io/s/ecstatic-meninsky-zx7w0?file=/src/App.vue:32-131
When I toggle checkbox, its value doesn't change.
Where am I wrong?
Thank you.

Checkboxes are a special case where they have two states (checked / unchecked) which can be translated to two values. Normally, this is simply true / false but since you've bound a single :value, you'll only ever get the same value each time.
Try binding the Boolean value prop to checked and emit a true / false value
<input
type="checkbox"
:checked="value"
:id="_uid"
#input="$emit('input', $event.target.checked)"
class="checkbox"
>

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>

Vuejs v-movable with #click is not working

Im trying to implement a moveable text that can also be click and call the method. However when i try to put #click its not calling the function. Can anyone help me? Thank you..
App.vue
<template>
<div id="app">
<div>
<a #click="next()">
<movable class="testmove" posTop="222" posLeft="222" shiftKey="true"
><span>Shift Key Behavior</span></movable
>
</a>
</div>
</div>
</template>
<script>
import movable from "v-movable";
export default {
name: "app",
movable,
methmethods: {
next() {
alert("Hello");
},
},
};
</script>
<style>
body {
padding: 0;
margin: 0;
font-family: Helvetica, Arial;
}
.movable {
cursor: pointer;
}
.testmove {
/* display:block;
position: absolute; */
top: 0;
height: 150px;
width: 150px;
background: #333;
color: white;
}
.modaltitle {
background: blue;
display: block;
width: 100%;
color: white;
}
</style>
Here is the code:
https://codesandbox.io/s/cold-darkness-0zm03?file=/src/App.vue
There is a typo in your code methmethods which should be corrected to methods
You can use start event for the v-movable.
#start: fires immediately after the pointerdown event on the element

How to close a modal by clicking on the backdrop in Vue.js

I have created a simple reusable modal component using Vue.js and it works fine, but I want to make so that when I click on the backdrop the modal closes, how can I achieve this? I searched and found a similar question on stackoverflow:
vuejs hide modal when click off of it
And did the same that the accepted answer does, putting #click="$emit('close')" on the wrapper but the modal does not get closed by clicking the backdrop as it is in the provided example. Here is my code:
<template>
<div :class="backdrop" v-show="!showModal">
<div class="modal-wrapper">
<div class="modal-container" :class="size" #click="$emit('close')">
<span class="close-x" #click="closeModal">X</span>
<h1 class="label">{{label}}</h1>
<div class="modal-body">
<slot></slot>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'custom-modal',
data() {
return {
showModal: false
};
},
props: {
label: String | Number,
size: String,
backdrop: String
},
components: {
'custom-btn': customBtn
},
methods: {
closeModal() {
this.showModal = true;
}
}
};
</script>
<style>
.modal-wrapper {
display: table-cell;
vertical-align: middle;
}
.modal-container {
margin: 0px auto;
padding: 20px 30px;
border-radius: 2px;
background-color: #fff;
font-family: Helvetica, Arial, sans-serif;
box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
transition: all .3s ease;
}
.close-x {
color: #00A6CE;
float: right;
}
.close-x:hover {
cursor: pointer;
}
</style>
Without a library you need to set it up like this:
<div class="modal-wrapper" #click="$emit('close')>
<div class="modal-container" :class="size" #click.stop=""></div>
</div>
It looks like you're missing the #click.stop="" which is required. Additionally you want to move the $emit('close') up to the modal-wrapper level.
With a library it may be overkill, but this is something that I have used v-click-outside for.
Vue directive to react on clicks outside an element without stopping the event propagation. Great for closing dialogues, menus among other things.
Simply npm install --save v-click-outside
Then (from the docs):
<div v-click-outside="onClickOutside"></div>
and:
onClickOutside (event, el) {
this.closeModal();
},
Try creating a transparent div that covers all the screen but with a z-index < your modals z-index. Then #click on it, you emit your event to close the modal :) Hope it will hellp
<template>
<div #click="handleBackdropClick" class="backdrop" ref="backdrop">
<div class="modal">
<h1> Modal Title </h1>
<input type="text" />
<p> Modal Content </p>
</div>
</div>
</template>
<style>
.modal {
width: 400px;
padding: 20px;
margin: 100px auto;
background: white;
border-radius: 10px;
}
.backdrop{
top: 0;
position: fixed;
background: rgba(0,0,0,0.5);
width: 100%;
height: 100%;
}
.close{
display: none;
}
</style>
export default {
methods: {
handleBackdropClick(e){
console.log(e)
if (e.path[0].className == "backdrop") {
this.$refs.backdrop.classList.add('close');
}
}
}
}
</script>

How to make a link as a file input in vue.js 2?

My vue component like this :
<template>
...
<a href="javascript:;" class="thumbs"
:title="upload">
<span class="fa fa-plus fa-2x"></span>
</a>
...
</template>
<script>
export default {
props: ['...'],
data() {
return {
...
};
},
computed:{
...
}
}
</script>
I want if click the a link, it can upload file
In javascript, I know it. If javascript like this : How to make a link act as a file input
But How can I do it in vue.js 2?
I believe there is a small misunderstanding: Vue.js 2 is still javascript. Its goal is not the same as Polymer with its fancy components - it is supposed to enhance JS, not replace it with a different structure altogether.
#David Hallberg Jönsson's answer will work perfectly fine in Vue.js 2 perfectly fine. If you want it specifically in Vue's component structure:
<template>
<!-- ... -->
<a class="fileContainer">
Click here to trigger the file uploader!
<input type="file">
</a>
<!-- ... -->
</template>
<script>
export default {
props: ['...'],
data() {
return {
...
};
},
computed:{
...
}
}
</script>
<style>
a.fileContainer {
overflow: hidden;
position: relative;
cursor: pointer;
display: inline-block;
color: lightskyblue;
}
a.fileContainer:hover {
text-decoration: underline;
color: blue;
}
a.fileContainer > input[type=file] {
cursor: inherit;
filter: alpha(opacity=0);
opacity: 0;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: right;
}
</style>
If you want to use the programmatic way in your link, it's not going to be easy because some browsers don't allow you to trigger click events on input type="file" elements. Your best bet would be to go this way.
(Also, technically you can still use jQuery with Vue, so the code in that link could still work if you wanted it to.)
If you want to know how to handle uploading files, there are many tutorials and some components already pre-made.
You can actually do this using only CSS, as explained here.
Example (from the link above):
.fileContainer {
overflow: hidden;
position: relative;
}
.fileContainer [type=file] {
cursor: inherit;
display: block;
font-size: 999px;
filter: alpha(opacity=0);
min-height: 100%;
min-width: 100%;
opacity: 0;
position: absolute;
right: 0;
text-align: right;
top: 0;
}
/* Example stylistic flourishes */
.fileContainer {
background: red;
border-radius: .5em;
float: left;
padding: .5em;
}
.fileContainer [type=file] {
cursor: pointer;
}
}
<p>So various methods prevent file upload inputs from being styled conveniently. But that needn't be the case!</p>
<label class="fileContainer">
Click here to trigger the file uploader!
<input type="file"/>
</label>

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;
}