Vue.js radio button no reactive - javascript

I am trying to pass the radio button value to its parent
here is the example:
Child
<template>
<radio v-model="type" value="TypeOne" #change="TypeOne()"></el-radio>
<radio v-model="type" value="TypeTwo" #change="TypeTwo()"></el-radio>
</template>
<script>
export default {
methods: {
TypeOne() {
this.$emit('input', 'TypeOne');
},
TypeTwo() {
this.$emit('input', 'TypeTwo');
}
}
}
</script>
parent
<template>
<component #input="onTypeChange"></component>
<div v-show="type"></div>
<div v-show="!type"></div>
</template>
<script>
export default {
data() {
return {
type: 'true',
};
},
methods: {
onTypeChange () {
var TypeOne = false
var TypeTwo = false
if (TypeOne == TypeOne) {
this.type = false;
} else if (TypeTwo == TypeTwo) {
this.type = true;
} else {
console.log('typeChanged');
return false;
}
}
}
}
</script>
I am trying to use radio data to hide or show some element in the parent component. in initial selection it works but after radio selection is changed it does not update the parent. How can I make the radio data reactive in my case?

You passed a parameter when you called this.$emit('input', 'TypeOne');
but the method emitted is not accepting a parameter.
Add a parameter on onTypeChange() and use the parameter on your logic
onTypeChange (type) {
if (type == 'TypeOne') {
this.type = false;
} else if (type == 'TypeTwo') {
this.type = true;
} else {
console.log('typeChanged');
return false;
}
}

Related

VueJS child to parent boolean value for hide component

Hi all and thank for helping :)
Context :
In my child i want pass a boolean to the parent for hidden a component in the parent if user clicked
in my child component ( name : connexionDesktop ) :
<button v-on:click="$emit(childMessage)"> Start </button>
data () {
return {
childMessage: true
}
}
in the parent i try :
<connexionDesktop v-if="fromChild == false " v-on:childToParent="childMessage"> </connexionDesktop>
data () {
fromChild:false
}
methods: {
childMessage (value) {
alert('from child' + value );
this.fromChild = value
}
}
but that's not working ... i think i am a noob :D i can't send a message from child to parent ^^""
can you help me ? thank a lot
The first argument for the $emit method should be the event name. So your code should look better like this:
// child component
<template>
<button v-on:click="handleClick"> Start </button>
</template>
<script>
export default {
data () {
return {
childMessage: true
}
},
methods: {
handleClick() {
this.$emit('update:parent', this.childMessage)
}
}
}
</script>
Then in the parent component, you listen for the event and pass in the value the child emitted to a local value of the parent like so:
<template>
<connexionDesktop v-if="fromChild == false" #update:parent="fromChild = $event">
</connexionDesktop>
</template>
<script>
export default {
data () {
return {
fromChild: false
}
},
}
</script>
As it's stated in the docs on $emit here, the first parameter is the custom event name.
You can do something like this:
<button v-on:click="$parent.$emit('childToParent', childMessage)"> Start </button>
data () {
return {
childMessage: true
}
}
and in the parent:
<connexionDesktop v-if="fromChild == false " v-on:child-to-parent="childMessage"> </connexionDesktop>
...
data () {
fromChild:false
}
methods: {
childMessage (value) {
alert('from child' + value );
this.fromChild = value
}
}
...

How to call the parent boolean with function to child in angular

I have boolean which is the toggleUnit and I have a function which is the onSwitch(). I also used nz-switch for the weather it its Fahrenheit or Celcius
list.component.html
<nz-switch [(ngModel)]="toggleUnit" nzCheckedChildren="°F" nzUnCheckedChildren="°C" (ngModelChange)="onSwitchChange()"></nz-switch>
<div class="weather">
<app-weather></app-weather>
</div>
list.component.ts
toggleUnit: boolean = false;
onSwitchChange() {
switch (this.toggleUnit) {
case true: {
this.toggleUnit = true;
}case false: {
this.toggleUnit = false;
}
}
}
weather.component.ts
ngOnInit() {
//console.log(this.toggleUnit);
//trying to get the parent ``toggleUnit`` value, if its true or false
}
You'll have to pass the value to your child component
list.component.html
<nz-switch [(ngModel)]="toggleUnit" nzCheckedChildren="°F" nzUnCheckedChildren="°C" (ngModelChange)="onSwitchChange()"></nz-switch>
<div class="weather">
<app-weather [toggleUnit]="toggleUnit"></app-weather>
</div>
weather.component.ts
#Input() toggleUnit: boolean;
ngOnInit() {
//console.log(this.toggleUnit);
//trying to get the parent ``toggleUnit`` value, if its true or false
}
ngOnChanges(value) {
// called when toggleUnit is updated from parent
console.log(value);
console.log(this.toggleUnit);
}

Prevent Select Input value change conditionally

I have this vue component:
<template>
<div id="OrderTypeSelect" class="order-type-select">
<b-form-select v-model="curDocType" :options="options" class="mb-3">
</b-form-select>
</div>
</template>
the value of the select input is bound to the Vuex store like this:
computed: {
curDocType: {
get () {
return this.$store.state.curDocType
},
set (value) {
this.$store.commit('setcurDocType', value)
}
}
}
What I can't figure out is how to conditionally prevent the select value from changing. I've tried this:
computed: {
curDocType: {
get () {
return this.$store.state.curDocType
},
set (value) {
if (this.$store.state.curOrder == ""){
this.$store.commit('setcurDocType', value)
}
else{
console.log("Debe descartar");
this.curDocType.get() //This throws error!
}
}
}
}
Even if I don't commit the value to the store, the value in the input field is changed.
I need to call get() again (or something else) to make this binding persistent when my condition is triggered:
if (this.$store.state.curOrder != "") {
//Do not modify store and return the input selection to it's previous value
}
In your case i recommend to use a data object item called curDocType and watch it instead of using computed property :
<b-form-select v-model="curDocType" :options="options" class="mb-3">
data object :
data(){
return{
curDocType:this.$store.state.curDocType
};
}
watch property :
watch:{
curDocType(newval,oldval){
if (this.$store.state.curOrder == ""){
this.$store.commit('setcurDocType', newval)
}else{
this.$nextTick(() => {this.curDocType=this.$store.state.curDocType})
}
}
}
Try <b-form-select v-model="curValue" #input="setValue" :options="options" class="mb-3">
Where curValue is a variable in data and setValue is a method:
methods: {
setValue(value) {
if (this.$store.state.curOrder == "") {
this.$store.commit('setcurDocType', value)
this.curValue = value
} else {
console.log("Debe descartar");
this.$nextTick(() => {this.curValue = this.$store.state.curDocType})
}
}
}

VueJs watching deep changes in object

I have this 3 components in VueJS. The problem i want to solve is: When i click at vehicle component, it needs to be selected (selected = true) and other vehicles unselected.
What i need to do for two-way data binding? Because i'm changing this selected property in VehiclesList.vue component and it also need to be changed in Monit.vue (which is a parent) and 'Vehicle.vue' need to watch this property for change class.
Also problem is with updating vehicles. In Monit.vue i do not update full object like this.vehicles = response.vehicles, but i do each by each one, and changing only monit property.
Maybe easier would be use a store for this. But i want to do this in components.
EDITED:Data sctructure
{
"m":[
{
"id":"v19",
"regno":"ATECH DOBLO",
"dt":"2017-10-09 13:19:01",
"lon":17.96442604,
"lat":50.66988373,
"v":0,
"th":0,
"r":0,
"g":28,
"s":"3",
"pow":1
},
{
"id":"v20",
"regno":"ATECH DUCATO_2",
"dt":"2017-10-10 01:00:03",
"lon":17.96442604,
"lat":50.6698494,
"v":0,
"th":0,
"r":0,
"g":20,
"s":"3"
},
]
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
getMonitData(opt){
let self = this;
if (this.getMonitDataTimer) clearTimeout(this.getMonitDataTimer);
this.axios({
url:'/monit',
})
.then(res => {
let data = res.data;
console.log(data);
if (!data.err){
self.updateVehicles(data.m);
}
self.getMonitDataTimer = setTimeout(()=>{
self.getMonitData();
}, self.getMonitDataDelay);
})
.catch(error => {
})
},
updateVehicles(data){
let self = this;
if (!this.vehicles){
this.vehicles = {};
data.forEach((v,id) => {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
});
} else {
data.forEach((v,id) => {
if (self.vehicles[v.id]) {
self.vehicles[v.id].monit = v;
} else {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
}
});
}
},
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehiclesList.vue
<template>
<div class="vehicles-list" :class="{'vehicles-list--short': isShort}">
<ul>
<vehicle
v-for="v in vehicles"
:key="v.id"
:data="v"
#click.native="select(v)"
></vehicle>
</ul>
</div>
</template>
<script>
import Vehicle from '#/components/modules/monit/VehiclesListItem.vue';
export default {
data: function(){
return {
isShort: true
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
},
components:{
Vehicle
}
}
</script>
Vehicle.vue
<template>
<li class="vehicle" :id="data.id" :class="classes">
<div class="vehicle-info">
<div class="vehicle-info--regno font-weight-bold"><span class="vehicle-info--no">{{data.no}}.</span> {{ data.monit.regno }}</div>
</div>
<div class="vehicle-stats">
<div v-if="data.monit.v !== 'undefined'" class="vehicle-stat--speed" data-name="speed"><i class="mdi mdi-speedometer"></i>{{ data.monit.v }} km/h</div>
</div>
</li>
</template>
<script>
export default {
props:{
data: Object
},
computed:{
classes (){
return {
'vehicle--selected': this.data.selected
}
}
}
}
</script>
Two-way component data binding was deprecated in VueJS 2.0 for a more event-driven model: https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
This means, that changes made in the parent are still propagated to the child component (one-way). Changes you make inside the child component need to be explicitly send back to the parent via custom events: https://v2.vuejs.org/v2/guide/components.html#Custom-Events or in 2.3.0+ the sync keyword: https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
EDIT Alternative (maybe better) approach:
Monit.vue:
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles" v-on:vehicleSelected="onVehicleSelected"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
onVehicleSelected: function (id) {
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
...other methods
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehicleList.vue:
methods:{
select(vehicle){
this.$emit('vehicleSelected', vehicle.monit.id)
}
},
Original post:
For your example this would probably mean that you need to emit changes inside the select method and you need to use some sort of mutable object inside the VehicleList.vue:
export default {
data: function(){
return {
isShort: true,
mutableVehicles: {}
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.mutableVehicles[id].selected = true;
this.$emit('update:vehicles', this.mutableVehicles);
},
vehilcesLoaded () {
// Call this function from the parent once the data was loaded from the api.
// This ensures that we don't overwrite the child data with data from the parent when something changes.
// But still have the up-to-date data from the api
this.mutableVehilces = this.vehicles
}
},
components:{
Vehicle
}
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles.sync="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
You still should maybe think more about responsibilities. Shouldn't the VehicleList.vue component be responsible for loading and managing the vehicles? This probably would make thinks a bit easier.
EDIT 2:
Try to $set the inner object and see if this helps:
self.$set(self.vehicles, v.id, {
monit: v,
no: Object.keys(self.vehicles).length + 1,
selected: false
});

Can't update state property? React

I am attempting to validate an input manually by passing up the chain the input of a text field. Some magic is supposed to happen whereby the following conditions are checked:
if input text matches that already held in an array - error = "already exists" & the text isn't added to the list
if input text is blank - error = "no text input" & the text isn't added to the list
if input text is not blank and does not already exist - run another method to add text to the list
The error is set to null by default
Currently in the input.js file, the {this.props.renderError} line causes an "underfined" output in the console before anything happens. I understand why this occurs, but I wondered if there was any way to stop it?
Functionality-wise: I can get the error message to output, however this appears to run after the text is already placed in the list of tasks...
Checkout the sandbox for this code
App.js (parent)
const tasks = [
{ name: 'task1', isComplete: false },
{ name: 'task2', isComplete: true },
{ name: 'task3', isComplete: false },
]
class App extends React.Component {
constructor() {
super();
this.state = {
error: null,
}
}
render() {
return (
<div>
<Input
createTask={this.createTask.bind(this)}
renderError={this.renderError.bind(this)}
taskList={this.state.tasks}
throwError={this.throwError.bind(this)}
/>
</div>
)
}
createTask(task, errorMsg) {
this.throwError(errorMsg);
if (this.state.error) {
return;
} else {
this.setState((prevState) => {
prevState.tasks.push({ name: task, isComplete: false });
return {
tasks: prevState.tasks
}
})
}
}
throwError(errorMsg) {
if (errorMsg) {
this.setState((prevState) => {
prevState.error = errorMsg;
return {
error: prevState.error
}
})
}
return;
}
renderError() {
if (this.state.error) {
return <div style={{ color: 'red' }}>{this.state.error}</div>
}
}
Input.js (child)
render() {
return (
<form ref="inputForm" onSubmit={this.handleCreate.bind(this)}>
<TextField placeholder="Input.js"/>
<Button type="submit">Click me</Button>
{this.props.renderError()}
</form>
)
}
validateInput(taskName) {
if (!taskName) {
return '*No task entered';
} else if (this.props.taskList.find(todo => todo.name.toLowerCase() === taskName.toLowerCase())) {
return '*Task already exists'
} else {
return null;
}
}
handleCreate(event) {
event.preventDefault();
// Determine task entered
var newTask = this.refs.inputForm[0].value;
// Constant for error message returned
const validInput = this.validateInput(newTask);
// If error message produced - trigger error to be shown & end
if (newTask) {
this.props.createTask(newTask, validInput);
this.refs.inputForm.reset();
}
}
Update
I have since found that I can make this work if I move the renderError and throwError methods to input.js and also transfer across the state property error.

Categories