Vue does not track changes on customizableFormData property while I change the input.
Simplified source code:
<template>
<input type="checkbox" #input="setFormField(form.name, 'does_attend', $event.target.checked)">
</template>
<script>
export default {
data() {
return {
form: {}//it's set by AJAX on mounted event,
customizableFormData: []
}
},
methods: {
setFormField(formName, inputName, value) {
if(this.customizableFormData[formName] === undefined) {
this.customizableFormData[formName] = {};
}
this.customizableFormData[formName][inputName] = value;
console.log(this.customizableFormData); // shows correct data
this.$forceUpdate();
}
}
}
</script>
Related
I feel like I am about to go down a path of extreme inefficiency when trying to keep data correctly mapped between a Parent and Child component.
If I have a simple Child Vue element like below
common/InputText.vue
<template>
<input v-bind:id="name" v-bind:value="value" v-on:input="changed($event, $event.target.value)">
</template>
<script>
props: ['name', 'value'],
methods: {
changed(event, value) { this.$emit('emitChanged', event, value); }
}
</script>
If I have a Parent Vue element like below, it is binding data to the Child elements. The problem is that it seems to be only binding from the Parent to the Child, the Parent data is not updating
Parent.vue
<input-text name="field01" v-bind:value="field01" #emitChanged="changed"></input-text>
<input-text name="field02" v-bind:value="field02" #emitChanged="changed"></input-text>
<script>
import inputText from "./common/InputText.vue";
export default {
data() {
return() {
field01: '',
field02: ''
}
},
components: {
input-text: inputText
},
changed(event, newValue) {
console.log(newValue);
}
}
</script>
I am able to update the Parent data with whatever the data the Child returns by changing the changed method to the below
changed(event, newValue) {
console.log(newValue);
if( event.target.id == 'field01' ) {
this.field01 = newValue;
}
if( event.target.id == 'field02' ) {
this.field02 = newValue;
}
}
This feels like a hack though and will become unmanageable should there be many input fields. What is the correct way to reupdate the Parent data?
This is why the v-model is useful, you can change your code in following way to overcome your problem without using v-model. but I would recommend try to implement v-model way.
<template>
<input v-bind:id="name" v-bind:value="value" v-on:input="changed($event, $event.target.value)">
</template>
<script>
props: ['name', 'value'],
methods: {
changed(event) { this.$emit('emitChanged', event); }
}
</script>
<input-text name="field01" v-bind:value="field01" #emitChanged="changed($event, 'field01')"></input-text>
<input-text name="field02" v-bind:value="field02" #emitChanged="changed($event, 'field02'"></input-text>
<script>
import inputText from "./common/InputText.vue";
export default {
data() {
return() {
field01: '',
field02: ''
}
},
components: {
input-text: inputText
},
changed(event, field) {
this[field] = event.target.value
}
}
</script>
Currently we have an text input whose value is stored in our vuex store, passed in as props and retrieved via computed property. In that same vuex store we have a selection range (array of two numbers), which will allow text in the input to be selected/highlighted.
Here's a simplified version of the code:
<template>
<input type="text" v-model="value" #select="selectionHandler" />
</template>
<script>
export default {
props: ['props'],
computed: {
value: function() {
return this.props.value // some string
},
selectionRange: {
get: function () { return this.props.selectionRange }, // [number, number]
set: function (range) { /* dispatch store action to update, which will update props */ }
}
},
methods: {
selectionHandler(e) {
this.selectionRange = [e.currentTarget.selectionStart, e.currentTarget.selectionEnd]
}
}
}
</script>
The goal is to select/highlight the text (from the store) and update the store based on what the user selects. I'm planning to utilize setSelectionRange (docs). I'm thinking I could use refs and watchers, but that just seems like overkill.
I ended up creating a select function and using refs to select the input/textarea text. Initially I called select using Vue life cycle methods: mounted and updated. So the simplified version looked like this:
<template>
<input ref="someRefName" type="text" v-model="value" #select="selectionHandler" />
</template>
<script>
export default {
props: ['props'],
computed: {
value: function() {
return this.props.value // some string
},
selectionRange: {
get: function () { return this.props.selectionRange }, // [number, number]
set: function (range) { /* dispatch store action to update, which will update props */ }
}
},
methods: {
selectionHandler(e) {
this.selectionRange = [e.currentTarget.selectionStart, e.currentTarget.selectionEnd]
},
select() {
this.$refs['someRefName'].setSelectionRange(this.selectionRange[0], this.selectionRange[1])
}
},
mounted: function () {
if (this.selectionRange[1] !== 0) { this.select() }
},
updated: function () {
if (this.selectionRange[1] !== 0) { this.select() }
}
}
</script>
That seemed like a pretty good solution... until I realized "oh, that's not how browsers work." The selected/highlighted text gets cleared every time the input loses focus. So I changed it to use focus and blur handler functions. Now the text is only selected when the element is in focus. Something like this:
<template>
<input ref="someRefName" type="text" v-model="value" #focus="focusHandler" #select="selectionHandler" #blur="blurHandler" />
</template>
<script>
export default {
props: ['props'],
computed: {
value: function() {
return this.props.value // some string
},
selectionRange: {
get: function () { return this.props.selectionRange }, // [number, number]
set: function (range) { /* dispatch store action to update, which will update props */ }
}
},
methods: {
focusHandler() {
if (this.selectionRange[1] !== 0) { this.select() }
},
selectionHandler(e) {
this.selectionRange = [e.currentTarget.selectionStart, e.currentTarget.selectionEnd]
},
blurHandler() {
this.selectionRange = [0, 0]
},
select() {
this.$refs['someRefName'].setSelectionRange(this.selectionRange[0], this.selectionRange[1])
}
}
}
</script>
There is the following code when working with v-model and html with <input type ="text"> tags:
<template>
<InputTextApp class="inputTextAdditionData" placeholder_text="" v-model="cell_phone_number">
</InputTextApp>
</template>
<script>
import InputTextApp from '~/components/FormElements/InputTextApp';
export default{
data () {
return {
loading_cell_phone_number: '',
}
},
computed: {
cell_phone_number: {
get () {
return this.loading_cell_phone_number;
},
set (value) {
this.loading_cell_phone_number = value;
}
},
}
</script>
Question:
If in the content of calculated properties it is necessary to have the above code, how should I proxy the data from getters the what would it is code working fine?
As a primitive test, I tried to do something like this:
// vuex storage: tracker.js
const axios = require("axios");
export const getters = {
personTypeInput3: (state) => {
return {index: {
get () {
return this.loading_cell_phone_number;
},
set (value) {
this.loading_cell_phone_number = value;
}
},
}}
};
<template>
<InputTextApp class="inputTextAdditionData" placeholder_text="" v-model="cell_phone_number">
</InputTextApp>
</template>
<script>
import InputTextApp from '~/components/FormElements/InputTextApp';
export default{
data () {
return {
loading_cell_phone_number: '',
}
},
computed: {
cell_phone_number: {
...mapGetters("tracker", [
"personTypeInput3",
])
}
</script>
Then I accepted the content code in a computed property of the following form:
What needs to be written in the storage getter in order to get the code specified in the very first implementation (at the beginning of the post) in the computed property at the output?
(something like this:)
For case below, when I customize a vue component of radio, I can use model option to get the v-model value which should be a string '1'.How can I get its variable name 'radio1' in the child? It's there any way?
the child component
<template>
<input type="radio" v-model="prop" :value="value">
</template>
<script>
export default {
model: {
prop: "prop"
},
props: {
prop: {
default: ''
},
value: {
default: ''
}
}
}
</script>
the parent use this component
<template>
<div>
<radio-component v-model="radio1" value="1"></radio-component>
<radio-component v-model="radio1" value="2"></radio-component>
</div>
</template>
<script>
import radioComponent from './radio'
export default {
components: {
radioComponent
},
data () {
return {
radio1: '1'
}
}
}
</script>
You can emit an event with any name you want, so in the child you can write:
<input type="radio" #input="$emit('prop', prop)" v-model="prop" :value="value">
Then in the parent you can do:
<radio-component v-model="radio1" #prop="doSomething" value="1" name="nameValue"></radio-component>
.
.
.
data(){
return {
nameValue: null
}
methods: {
doSomthing(prop){
this.nameValue = prop
}
}
I have a simple application which need to render 2 components dynamically.
Component A - needs to have onClick event.
Component B - needs to have onChange event.
How is it possible to dynamically attach different events to component A/B?
<template>
<component v-bind:is="currentView">
</component>
</template>
<script>
import A from '../components/a.vue'
import B from '../components/b.vue'
export default {
data: function () {
return {
currentView: A
}
},
components: { A, B }
}
</script>
Here is a solution for a little more complicated and realistic use case. In this use case you have to render multiple different components using v-for.
The parent component passes an array of components to create-components. create-components will use v-for on this array, and display all those components with the correct event.
I'm using a custom directive custom-events to achieve this behavior.
parent:
<template>
<div class="parent">
<create-components :components="components"></create-components>
</div>
</template>
<script>
import CreateComponents from '#/components/CreateComponents'
import ComponentA from '#/components/ComponentA'
import ComponentB from '#/components/ComponentB'
export default {
name: 'parent',
data() {
return {
components: [
{
is: ComponentA,
events: {
"change":this.componentA_onChange.bind(this)
}
},
{
is: ComponentB,
events: {
"click":this.componentB_onClick.bind(this)
}
}
]
}
},
methods: {
componentA_onChange() {
alert('componentA_onChange');
},
componentB_onClick() {
alert('componentB_onClick');
}
},
components: { CreateComponents }
};
</script>
create-components:
<template>
<div class="create-components">
<div v-for="(component, componentIndex) in components">
<component v-bind:is="component.is" v-custom-events="component.events"></component>
</div>
</div>
</template>
<script>
export default {
name: 'create-components',
props: {
components: {
type: Array
}
},
directives: {
CustomEvents: {
bind: function (el, binding, vnode) {
let allEvents = binding.value;
if(typeof allEvents !== "undefined"){
let allEventsName = Object.keys(binding.value);
allEventsName.forEach(function(event) {
vnode.componentInstance.$on(event, (eventData) => {
allEvents[event](eventData);
});
});
}
},
unbind: function (el, binding, vnode) {
vnode.componentInstance.$off();
}
}
}
}
</script>
You don't have to dynamically add them.
<component v-bind:is="currentView" #click="onClick" #change="onChange">
If you want to be careful you can bail in the handler of the currentView is not correct.
methods: {
onClick(){
if (this.currentView != A) return
// handle click
},
onChange(){
if (this.currentView != B) return
// handle change
}
}