Toggle button component how to reset from parent - vue.js

There is a toggle component, which I connect to the parent. Found a bug, haven't found a solution yet.
toggle-button
<template>
<label :for='id + "_button"' :class='{"active": isActive}' class='toggle__button'>
<input type='checkbox' :id='id + "_button"' v-model='checkedValue'>
<span class='toggle__switch'></span>
</label>
</template>
<script>
export default ({
props: {
defaultState: {
type: Boolean,
default: false
},
id: {
type: String,
default: 'primary'
}
},
data() {
return {
currentState: this.defaultState
};
},
computed: {
isActive() {
return this.currentState;
},
checkedValue: {
get() {
return this.defaultState;
},
set(newValue) {
this.currentState = newValue;
this.$emit('change', newValue);
}
}
},
methods: {
reset() {
this.currentState = false ;
}
}
});
</script>
how can i succinctly make a reset button? I use this option now, but after reset, when I click on toggle, it does not work the first time.
<button
#click='
$refs.toggleOriginal.reset($event),
$refs.toggleAnalog.reset($event),
$refs.toggleAvailable.reset($event)
'
>
reset
</button>
Each toggle has ref in parent.

I think what you may be saying is that you have a toggle button that can flip a value anywhere (perhaps stored in a higher state?) and a reset button that can set the value back to it's initial default.
Keep the management of this data property handled outside of toggle, and make toggle responsible for simply flipping the value, and reset responsible for resetting it.
Here is what I would do:
// parent.vue
<template>
<div>
Toggled value: {{toggleValue}}
<resetButton v-model="toggleValue" />
<toggleButton v-model="toggleValue" />
</div>
</template>
<script>
export default {
data() {
return {
toggleValue: false,
}
}
}
</script>
// toggleButton.vue
<template>
<button #click="toggle">Toggle</button>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
}
},
computed: {
toggleValue: {
get() {
return this.value;
},
set(val) {
this.$emit('input', val);
}
}
},
methods: {
toggle() {
this.toggleValue = !this.toggleValue
}
}
}
</script>
// resetButton.vue
<template>
<button #click="reset">Reset</button>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
}
},
methods: {
reset() {
this.$emit('input', false);
}
}
}
</script>

Related

How to make this vue component reusable with it's props?

I am trying to make this component reusable so later can install it in any project and via props add needed values e.g. images and function parameters (next, prev, intervals...) inside any component.
<template>
<div>
<transition-group name='fade' tag='div'>
<div v-for="i in [currentIndex]" :key='i'>
<img :src="currentImg" />
</div>
</transition-group>
<a class="prev" #click="prev" href='#'>❮</a>
<a class="next" #click="next" href='#'>❯</a>
</div>
</template>
<script>
export default {
name: 'Slider',
data() {
return {
images: [
'https://cdn.pixabay.com/photo/2015/12/12/15/24/amsterdam-1089646_1280.jpg',
'https://cdn.pixabay.com/photo/2016/02/17/23/03/usa-1206240_1280.jpg',
'https://cdn.pixabay.com/photo/2015/05/15/14/27/eiffel-tower-768501_1280.jpg',
'https://cdn.pixabay.com/photo/2016/12/04/19/30/berlin-cathedral-1882397_1280.jpg'
],
timer: null,
currentIndex: 0,
}
},
mounted: function() {
this.startSlide();
},
methods: {
startSlide: function() {
this.timer = setInterval(this.next, 4000);
},
next: function() {
this.currentIndex += 1
},
prev: function() {
this.currentIndex -= 1
}
},
computed: {
currentImg: function() {
return this.images[Math.abs(this.currentIndex) % this.images.length];
}
}
}
</script>
styles...
So later it would be <Slider... all props, images loop here/> inside other components.
How can be it be achieved?
Just move what needs to come from another component to props. That way other component can pass the relevant info it needs.
export default {
name: 'Slider',
props: {
images: Array,
next: Function
prev: Function,
// and so on
},
...
The parent component would call it like:
<Slider :images="imageArray" :next="nextFunc" :prev="prevFunc" />
EDIT
You can pass an interval value via props:
export default {
name: 'Slider',
props: { intervalVal: Number },
methods: {
startSlide: function() {
this.timer = setInterval(this.next, this.intervalVal);
},
}
You can also pass function from parent to child via props.
export default {
name: 'Slider',
props: { next: Function },
methods: {
someMethod: function() {
this.next() // function from the parent
},
}
I don't really understand your use case 100% but these are possible options.

VueJs getting child form data from parent component

I have a form in my child component:
<form #submit="submitForm">
<input type="text" v-model="textInput" />
</form>
export default {
name: "childComp",
data: function() {
return {
textInput: ""
}
}
}
Now from my parent component I have a button and I need to click on that button to get the child form data.
<button type="button" #click="getFormData()"> click </button>
export default {
name: "ParentComp",
data: function() {
return {
formData: []
}
},
methods: {
getFormData(d) {
formData.push(d);
console.log(d)
}
}
}
Appreciate your help.
Even though you got it solved by using $ref, i would recommend utilizing the power of a v-model implementation into your custom component.
This is a more clean approach, and by doing this, you'll always have the form data at hand, instead of having to actually retrieve it when you want to use it.
It can be done by doing the following:
Parent
<button type="button" #click="getFormData()"> click </button>
<childComp v-model="formData" />
export default {
name: "ParentComp",
data: function() {
return {
formData: {}
}
},
methods: {
getFormData() {
console.log(this.formData)
}
}
}
Child
<form>
<input type="text" v-model="selected.text" />
</form>
export default {
name: "childComp",
props: ['value'],
computed: {
selected: {
get() {
return this.value;
},
set(value) {
this.$emit('input', value);
}
}
}
}
I found the solutions using ref="". Not sure how complex it will get in future.
Here is what I did.
My parent component:
<button type="button" #click="getFormData()"> click </button>
<childComp ref="childComp" />
export default {
name: "ParentComp",
data: function() {
return {
formData: []
}
},
methods: {
getFormData() {
const data = this.$refs.childComp.submitForm()
formData.push(data);
console.log(data)
}
}
}
My child component:
<form #submit="submitForm">
<input type="text" v-model="textInput" />
</form>
export default {
name: "childComp",
data: function() {
return {
textInput: ""
}
},
submitForm() {
return this.form;
}
}

VueJS set custom component default value from props

Hi guys I tried to create a VueJS custom component to wrap Vue Autonumeric component.
https://github.com/autoNumeric/vue-autoNumeric
In Vue Autonumeric page it specifically mention the caveat
Caveats Please note that directly setting a :value='42' on the
component will break it (really!). Do NOT do that:
So in my custom component MoneyComponent.vue, I create a v-model
This is the full code
<template>
<div>
<vue-autonumeric
v-model="amount"
></vue-autonumeric>
</div>
</template>
<script>
import VueAutonumeric from 'vue-autonumeric/src/components/VueAutonumeric.vue';
export default {
components: {
VueAutonumeric,
},
props: {
value: {},
},
data() {
return {
amount: this.value,
}
},
methods: {
},
watch: {
amount (value) {
this.$emit('input', value);
}
},
}
</script>
Usage example
<template>
<v-money
v-model="price"
></v-money>
</template>
<script>
export default {
data() {
return {
price: 45,
}
},
methods: {
}
}
<script>
This works on on initial value from parent. However if I change the price property to 55 for example, the amount property in MoneyComponent is not changing.
What is the problem here the amount property is not reactive on second changes? How do I fix it?
Thanks
Because you're using v-model, you need to emit input event to make data changed in the parent
watch: {
amount: function (newVal) {
this.$emit('input', newVal)
},
value: function (newVal, oldVal) {
if (newVal !== oldVal) {
this.amount = newVal
}
}
}
then your component
<template>
<div>
<vue-autonumeric
v-model="amount"
></vue-autonumeric>
</div>
</template>
<script>
import VueAutonumeric from 'vue-autonumeric/src/components/VueAutonumeric.vue';
export default {
components: {
VueAutonumeric,
},
props: {
value: {},
},
data() {
return {
amount: this.value,
}
},
watch: {
amount: function (newVal) {
this.$emit('input', newVal)
},
value: function (newVal, oldVal) {
if (newVal !== oldVal) {
this.amount = newVal
}
}
}
}
</script>

Vue computed property not recomputing as I would have expected

I'm trying to create a generic search component with an event that gets emitted with the search string back to the parent so the parent can actually filter the results.
With the code below why does computed.filteredDocuments not recompute when the value of this.searchCriteria changes and how can I tweak my code so that it does recompute when updatedSearchString is called?
Parent component
<template>
<search :searchCriteria="searchCriteria" #searchString="updatedSearchString" />
<div v-for="(doc, index) in filteredDocuments" v-bind:key="index">
<div>{{doc.filename}}</div>
</div>
</template>
<script>
import store from '../store/index'
import { mapState } from 'vuex'
// import _ from 'lodash'
import Search from '../components/search'
export default {
name: 'Parent',
components: {
Search: Search
},
data () {
return {
searchCriteria: ''
}
},
computed: {
...mapState({
documents: state => state.documents.items
}),
filteredDocuments () {
console.log('in computed')
return _(this.documents)
.filter(this.applySearchFilter)
.value()
}
},
methods: {
updatedSearchString (searchString) {
this.searchCriteria = searchString <-- I WOULD HAVE EXPECTED BY UPDATING THIS IT WOULD TRIGGER COMPUTED.FILTEREDDOCUMENTS TO RECOMPUTE
}
},
applySearchFilter (doc) {
console.log('in applySearchFilter')
// If no search criteria return everything
if (this.searchCriteria === null) {
return true
}
if (doc.filename.toLowerCase().includes(this.searchCriteria.toLowerCase())) {
return true
}
return false
}
}
</script>
Child component
<template>
<div>
<q-search v-model="search" placeholder="Search" />
</div>
</template>
<script>
export default {
name: 'Search',
props: {
searchCriteria: { type: String, required: true }
},
data () {
return {
search: null
}
},
mounted () {
this.search = this.searchCriteria // Clone
},
watch: {
search: function (newVal, oldVal) {
// If no search criteria return everything
if (!newVal) {
this.clearSearch()
}
this.$emit('searchString', newVal) <-- THIS EMITS THE SEARCH VALUE TO THE PARENT
}
}
}
</script>
What I ran into turning your example code into a snippet was some of the camel case/kebab case issue: HTML is case-insensitive, so the event should be kebab-case, not camelCase. This example filters as expected.
new Vue({
el: '#app',
data: {
searchCriteria: '',
documents: [{
filename: 'FirstFile'
},
{
filename: 'SecondFile'
},
{
filename: 'LastOne'
}
]
},
methods: {
updatedSearchString(searchString) {
this.searchCriteria = searchString
},
applySearchFilter(doc) {
console.log('in applySearchFilter')
// If no search criteria return everything
if (this.searchCriteria === null) {
return true
}
if (doc.filename.toLowerCase().includes(this.searchCriteria.toLowerCase())) {
return true
}
return false
}
},
computed: {
filteredDocuments() {
console.log('in computed')
return this.documents
.filter(this.applySearchFilter)
}
},
components: {
search: {
template: '#child-template',
props: {
searchCriteria: {
type: String,
required: true
}
},
computed: {
search: {
get() {
return this.searchCriteria;
},
set(value) {
this.$emit('search-string', value);
}
}
}
}
}
});
<link href="https://unpkg.com/quasar-extras#latest/material-icons/material-icons.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/quasar-framework#latest/dist/umd/quasar.mat.min.css">
<script src="https://unpkg.com/quasar-framework#latest/dist/umd/quasar.mat.umd.min.js"></script>
<div id="app">
<search :search-criteria="searchCriteria" #search-string="updatedSearchString"></search>
<div v-for="(doc, index) in filteredDocuments" v-bind:key="index">
<div>{{doc.filename}}</div>
</div>
</div>
<template id="child-template">
<div>
<q-search v-model="search" placeholder="Search" />
</div>
</template>

Changing value in watching cann`t work in some case with Vue

I want to change number to int in watch, but it doesn`t work.
index.vue:
<template>
<test v-model="value" />
</template>
<script>
import Test from './test.vue';
export default {
components: {
test: Test,
},
data() {
return {
value: 1,
};
},
watch: {
value(val) {
this.value = parseInt(val);
// this.value = 2;
}
}
};
</script>
test.vue:
<template>
<div class="el-input-number">
<input
:value="currentValue"
#input="handleInput"
ref="input"
/>
</div>
</template>
<script>
export default {
name: 'ElInputNumber',
props: {
value: {
default: 0
},
},
data() {
return {
currentValue: 1
};
},
watch: {
value(value) {
console.log('value change', value);
let newVal = Number(value);
if (isNaN(newVal)) return;
this.currentValue = newVal;
this.$emit('input', newVal);
}
},
computed: {
},
methods: {
setCurrentValue(newVal) {
const oldVal = this.currentValue;
if (oldVal === newVal) return;
this.$emit('change', newVal, oldVal);
this.$emit('input', newVal);
this.currentValue = newVal;
},
handleInput(e) {
const value = e.target.value;
const newVal = Number(value);
if (!isNaN(newVal)) {
this.setCurrentValue(newVal);
}
}
}
};
</script>
The main code is this.value = parseInt(val).
The initial value is 1. I type 1.1 in input. Then I get changes in watching.
After I set this.value, the value watching function isn't called. There is no log.
What's more strange, if I change this.value = parseInt(val) to this.value = 2, it works once, but the second time I change the value, it doesn`t work.
Code is here: https://github.com/lext-7/vue-watch-component-demo.
Run it, and type 1.1 in that input. You will find that it won't become 1.