Stateless (controlled) input - vue.js

Stateless input means it changes only when :value binding of parent does change. Which gives full control over what it displays, which is useful for masks and filters.
What I have
This solution is the one closest to what I need: https://codesandbox.io/s/mm9n7r08mx
The problem with existing solution
Cursor jumps to the end when I try to type something in the middle of the existing text.
What I need
Any working solution for stateless input or a way to fix the existing one.
Materials I found
React issue
React fiddle for credit card input http://jsbin .com/dunutajuqo

It's jumping because you're manually assigning a value to the field. You don't need to re-set the value during input event. The value is already in sync at that point. Posting the full code blurb here so others have context:
<template>
<input
class="com-input"
:value="value"
#input="setValue"
:placeholder="placeholder"
>
</template>
<script>
export default {
name: "ComInput",
props: {
value: {
type: String
},
placeholder: {
type: String
}
},
methods: {
setValue($event) {
const value = $event.target.value;
$event.target.value = this.value; // <-- DELETE THIS
this.$emit("input", value);
}
}
};
</script>

Related

Vue3 model updates initially after saving code, but not after a browser refresh

I have a bit of a quantum problem happening. I have some code (below) that is not displaying the value of the data model. So I put a console.log in to see what's going on and then it works... none of the other code changes, but by looking at it, it exists. Then if I refresh the page, it's gone again
[Update] It's not the console.log itself (whew - that would be wierd). It's the state when I change the file vs when i do a fresh browser load. What's different there that I should be looking at? I'm new to VueJS, and I'm really more of a python / backend dev anyway, so sorry if this is basic stuff.
The template code in child component
<v-text-field v-else
#change="updateValue(field.name, $event)"
persistent-placeholder
:model-value="(item as any)[field.name]"
:label="field.title || field.name"
></v-text-field>
item is passed in as a Model and converted as follows
props: {
input: {
type: Object || null,
default: null
},
},
data() {
return {
item: {...this.input},
}
},
which comes from the parent
<ModelComponentVue
:input="item"
/>
which gets the data in created()
DataStore.query(User).then((items:Array<User>) => {
if (items.length > 0) {
this.item = items[0] // I suspect this could be not responding to reactivity in Vue3 ???
}
})
Parts of your code only use initialData contrary to what you might think. Pay attention to the code below.
data() {
return {
item: {...this.input},
}
},
In the above code, you will only have the initial value of the input and you will lose the other values.
The easiest way to use props is to use them directly.
However, for more complex concepts, you can use computed or toRef, etc.
Try the code below
<v-text-field v-else
#change="updateValue(field.name, $event)"
persistent-placeholder
:model-value="input[field.name]"
:label="field.title || field.name"
></v-text-field>
props: {
input: {
type: Object ,
default: () => {}
},
},

Reseting a config option on vue-flatpickr programatically

I have a vue date component that is composed of a vue-flatpickr-component. When I pass config options in as props, of course, they work as expected, however, if want to change one of the config options which should be possible, it won't propagate down. I'm not a Vue guru, any advice would be helpful.
I'm using a page component in a Laravel app, it shouldn't be relevant, however, just in case someone answers with vuex or vue-router, those won't work here.
Here are the form elements in play from page.vue:
<material-select
name="specialist"
label="Specialist"
default-text="CHOOSE HOMEVISIT SPECIALIST"
:options="staffMembers"
v-model="form.specialist"
:validation-error="form.errors.first('specialist')"
class="mb-4"
></material-select>
<div class="w-1/2">
<material-date
label="Appointment date"
name="appointment_date"
v-model="form.appointment_date"
:validation-error="form.errors.first('appointment_date')"
class="mb-4"
:external-options="{
enable: this.appointmentDates,
}"
></material-date>
<pre>{{ this.appointmentDates }}</pre>
</div>
Here is the computed property driving the config change:
computed: {
appointmentDates(){
if(this.form.specialist !== null){
return this.availableDates[this.form.specialist - 1]
}
return []
},
When a different home visit specialist is chosen, it will update with Vue's reactivity.
I have a computed property changing the config options. Here are the props data and the relevant computed property from the MaterialDate.vue file:
import flatPickr from 'vue-flatpickr-component';
import 'flatpickr/dist/flatpickr.css';
export default {
components: {
flatPickr
},
props: {
value: String,
label: String,
validationError: String,
name: {required:true},
optional: {
default: false
},
externalOptions: {}
},
data() {
return {
defaults: {disableMobile: true,},
options: this.externalOptions
}
},
computed: {
config(){
return Object.assign({}, this.defaults, this.options)
},
This will of course never update the enabled dates option because the prop is immutable, I need to get access to the set(option, value) section of the wrapped by vue-flatpickr-component. However, my Vue kungfu is not really strong enough to source dive it to see how I might access it and programatically call set('enabled', [new dates]).
Sometimes, you shouldn't code when you are tired :) But Hopefully this will help someone at some point. I was over thinking this. Data is passed down through props, and if controlling data changes it has to be reflected in the propagated data. Much like v-model with it's value prop.
So instead of binding the config object on this.options which doesn't stay hooked to it's prop value that it was initialized from, the computed function should be calculated from the prop which will change based on the new passed in options prop.
so simply change the computed function to:
computed: {
config(){
return Object.assign({}, this.defaults, this. externalOptions)
},
and remove the data element.
... Elementary
Sorry for the cheese it's late and I feel relieved.

Add focus to first input on load and next input after success using vue

I am creating a fill in the blank using vue. I have three components, a parent component where I build the question, an input component where I validate and the text component. I have stripped out a lot of the code to try and keep the question relevant, anything commented out is unsuccessful. Hope I am not out in left field on this one but have never attempted this so thought I would try here.
First issue:
Some of the questions have two inputs and I want to auto provide focus to the first input, (using a custom directive ) I am able to gain focus on the last created input. I am not really sure how to access first child I guess. Works well with single input questions though. I have tried doing so by using $refs and $nextTick(()) but no luck.
Second issue:
Once one input gets isCorrect I want to auto focus to the next available non correct element. I figured I would have be able to access the child input from the parent component and have been trying using this link but I have been unsuccessful.
Any help or insights is much appreciated. thanks
What it looks like for visual
What I am after
Parent Component
import ivInput from "../inline-components/iv-input.vue";
import ivText from "../inline-components/iv-text.vue";
<component
:is="buildQuestion(index)"
ref="ivWrap"
v-for="(item, index) in questionParts"
:key="index">
</component>
export default() {
components:{
ivInput,
ivText
},
mounted(){
// console.log(this.$refs.ivWrap)
// console.log(this.$refs.iv)
},
methods: {
buildQuestion: function (index) {
if(this.questionParts[index].includes('*_*')){
return ivInput
}else{
return ivText
}
},
//focus: function (){
// this.$refs.iv.focus()
// console.log(this.$refs.iv)
// }
}
}
Input Component
<div :class="'iv-input-wrap'">
<input
ref="iv"
v-focus
type="text"
v-model="userInput"
:class="[{'is-correct':isCorrect, 'is-error':isError}, 'iv-input']"
:disabled="isCorrect">
</div>
export default{
// directives:{
// directive definition
// inserted: function (el) {
// el.focus()
// },
}
computed{
isCorrect: function () {
if(this.isType == true && this.isMatch == true){
// this.$refs.iv.focus()
// this.$nextTick(() => this.$refs.iv.focus)
return true
}
}
}
}

vue: transforms a prop in its definition

I've a component which is a button with some stuff like a icon. I use it like this:
<ti-btn icon="..." #click.native="..."></ti-btn>
also, I can pass a prop which isn't required, this prop is the size, which is a Number.
<ti-btn icon="..." size="32" #click.native="..."></ti-btn>
Now, in my component's definition, at first, I wrote:
<template>
<i :class="icon" :style="{fontSize}"></i>
</template>
<script>
export default {
props: ['icon', 'size'],
computed: {
fontSize() {
return this.size ? `${this.size}px` : DEFAULT_SIZE;
}
}
}
</script>
If the size is not passed down, I set the default value. That's works, but according to good practices and vue style guide, Prop definitions should be as detailed as possible. So, I started to use this way:
props: {
icon: {
type: String,
required: true,
validate(value){
//some kind of validation here
return value.includes('ti')
}
},
size: {
type: Number,
required: false,
default: DEFAULT_SIZE
}
}
size prop receive a Number, but, it must return a string, in fact DEFAULT_SIZE is set to "24px" which is a String, also, the value receive is transformed to value+"px". So, My question is, how can I transform the size prop in its own definition object, without use the computed property?
As Terry mentioned in the comments, it's not possible to provide a way to transform the value of a prop being passed to a component from within the prop's definition itself.
You could let size just be a Number, and then add the 'px' when you bind it to the style:
<i :class="icon" :style="{ fontSize: `${size}px` }"></i>
This would mean you'd need to make DEFAULT_SIZE equal to 24.
If you're unable to change the value of DEFAULT_SIZE. Then your example of the fontSize computed property is the correct way to handle the issue.

Binding method result to v-model with Vue.js

How do you bind a method result to a v-model with Vue.js?
example :
<someTag v-model="method_name(data_attribute)"></someTag>
I can't make it work for some reason.
Thank you.
Years later, with more experience, I found out that is it easier to bind :value instead of using v-model. Then you can handle the update by catching #change.
Edit (per request):
<input :value="myValue" #change="updateMyValue">
...
methods: {
updateMyValue (event) {
myValue = event.target.value.trim() // Formatting example
}
}
And in a child component:
// ChildComponent.vue
<template>
<button
v-for="i in [1,2,3]">
#click="$emit('change', i) />
</template>
// ParentComponent.vue
<template>
<child-component #change="updateMyValue" />
</template>
<script>
import ChildComponent from './child-component'
export default {
components: {
ChildComponent
},
data () {
return {
myvalue: 0
}
},
methods: {
updateMyValue (newValue) {
this.myvalue = newValue
}
}
}
</script>
v-model expressions must have a get and set function. For most variables this is pretty straight forward but you can also use a computed property to define them yourself like so:
data:function(){
return { value: 5 }
},
computed: {
doubleValue: {
get(){
//this function will determine what is displayed in the input
return this.value*2;
},
set(newVal){
//this function will run whenever the input changes
this.value = newVal/2;
}
}
}
Then you can use <input v-model="doubleValue"></input>
if you just want the tag to display a method result, use <tag>{{method_name(data_attribute)}}</tag>
Agree with the :value and #change combination greenymaster.
Even when we split the computed property in get/set, which is help, it seems very complicated to make it work if you require a parameter when you call for get().
My example is a medium sized dynamic object list, that populates a complex list of inputs, so:
I can't put a watch easily on a child element, unless I watch the entire parent list with deep, but it would require more complex function to determine which of the innter props and/or lists changed and do what fromthere
I can't use directly a method with v-model, since, it works for providing a 'get(param)' method (so to speak), but it does not have a 'set()' one
And the splitting of a computed property, have the same problem but inverse, having a 'set()' but not a 'get(param)'