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

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

Related

Use v-if (with computed prop) inside v-for

Thanks for the help with displaying the edit-button only when i'm logged in.
The question I need help with is: Can I bind the whole {{ project.* }} which im looping through? to the "EditProject" component?
<EditProject :project="`${project}`">
Some info about my application:
I am using Vuex to store all my data. Ideally I would like to just pass the hole project to the dialog, but I could also just pass the ID and then load just that one project from Vuex. EditProject is a new dialog windows in which I would like to be able to edit the project.
I am creating a list of projects. The projects are stored with Vuex and Firebase, and are loaded as a computed prop.
computed: {
projects () {
return this.$store.getters.loadedProjects
}
}
Then i'm looping through all the projects and display title, date, description and creatorId.
<v-list>
<div v-for="project in projects" :key="project.id">
<p>{{ project.title }}</>
<edit-project v-if="isSignedIn === project.creatorId">
</div>
Here comes the tricky part. I want to display if project.creatorId is the same as the user logged in. "isSignedIn" is a computed prop:
isSignedIn () {
if (this.$store.getters.user !== null && this.$store.getters.user !== undefined) {
return true
} else return false
},
Summary: The v-for loop contains a {{ project.creatorId }} which I want chech if is the same as this.$store.getters.user, And then display the "Edit-project" if match. Edit-project is a button that opens a dialog. This is a simplified version of my application.
I have tried v-if="checkUserIdMethod(${project.id})", and all kind of variations, but had to resort to this website. I'm pretty sure I could get it to work with them backticks ``
Have you tried to filter the list before rendering it like :
computed: {
userProjects () {
return this.$store.getters.loadedProjects.filter(p => p.creatorId === this.$store.getters.user.id )
}
}
If you use it in many components you can also make the filter in the store and call it like :
computed: {
userProjects () {
return this.$store.getters.userLoadedProjects(userId)
}
}
You really should be looking to compare the current users id, instead of building extra methods to find out if the current user matches based on isSignedIn, which means you could write:
v-if="$store.getters.user.id === project.creatorId",
or alternatively refactor it into a separate method:
v-if="isCurrentUser(project.creatorId)"
isCurrentUser(creatorId) {
return this.$store.getters.user.id === creatorId;
}
also, if you still need isSignedIn, I would simplify it to increase readability:
isSignedIn () {
return this.$store.getters.user !== null && this.$store.getters.user !== undefined;
},

Stateless (controlled) input

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>

Can't copy props to model data and render it in Vue 2

I'm having this problem that looks a lot like a bug to me and I can't figure out how to solve it.
I created a generic list component and I tell it what child component it should insert in each item and what are the data it should pass to the child component. I'm passing everything as props along with the list (array) itself.
The problem is that I can't mutate the list props. So I try to copy it to model attribute. Otherwise I get this error:
Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders.....
And I can't just make it work in any of the lifecycle events. When I save the file and the hot-reloading reloads the page, the list is there, rendered, full of items. When I press F5 to manually reload the page, it is no more. Everything seems to be alright with code though
So in the parent component I'm doing this:
<List ref="link_list"
:list="this.foo.links" //this is array
:child="'LinkFormItem'" //this is the name of the child component
:section_name="'Links'"
:defaults="{content: '', type: 'facebook'}" />
In the List component I get this:
Template
<li class="" v-for="item in datalist">
<component :is="child" :item="item" ></component>
<button v-on:click='remove(index++)' type="button" name="button" class='red button postfix small'>Remove</button>
</li>
Script
<script>
import Child1 from './Child1'
import Child2 from './Child2'
export default {
name: 'search',
props: ['child', 'list', 'defaults','section_name'], //it is received as 'list'
components: {
Child1, Child2
},
data () {
return {
index: 0,
datalist: [] //i'm trying to copy 'list' to 'datalist'
}
},
beforeMount: function () {
// i'm copying it
for(var k in this.list){
this.datalist.push(this.list[k])
}
},
methods: {
//and here I should change it the way I want
add: function () {
this.datalist.push(this.defaults)
},
getList () {
return this.datalist;
},
remove(index){
var datalist = [];
for(var k in this.datalist){
if(k != index) datalist.push(this.datalist[k]);
}
this.datalist = datalist;
}
}
}
</script>
I don't see any problems with my Script. What is going on??
#edit
Ok, some console.log later I found out what the problem seems to be. The HTTP Request is really taking much longer than the mounting of the component to happen. But when it happens, it is not triggering the update in the list component. Nothing is re-rendered and the list is empty.
Workaround
well I realised the problem was related to propagation. I made a few changes in the code to asure the parent component was updating and changing the model value. but the child component (the list component) was not receiving it.
then I gave up trying to understand why and did the following:
1- used the ref in the child component to force an update in the child component with $forceUpdate and then I was assigning the props to the model in the beforeUpdate event. It was causing an error: an re-rendering loop. The update caused a new update and so on. We could just use a flag to stop it.
2- Instead I just called a child method directly:
this.$refs.link_list.updateList(data.links);
I hate this approach because I think it's way too explicit. But it did the job. Then in the child component a new method:
updateList(list){
this.datalist = list;
}
3- The other possibility that passed through my mind was emitting an event. But I didn't try, too complicated
You can simply do like as follows
data () {
return {
index: 0,
datalist: this.list // to copy props to internal component data
}
},
Once you done about you need to apply data manipulation opertions on new this.datalist , not on this.list
If you don't want to mutate the original list array you can do this:
data () {
return {
index: 0,
datalist: Object.assign({}, this.list)
}
}
I think this will help you

Dynamically add properties to Vue component

I am loading data from the database which drives what type of component I display
An AJAX call goes off and returns me some data (this can be restructured if needed)
{
component_type: 'list-item',
props: {
name: 'test',
value: 'some value'
}
}
This is accessible on my parent object a variable called component
Within the template of my parent object I have the following
<component :is="component.component_type" ></component>
This works fine and loads the component as expected.
Next I want to add the properties from my data object into this tag too
<component :is="component.component_type" {{ component.props }} ></component>
This doesn't work and rejects writing a tag with {{ in it. I presume this is an error thrown by the browser rather than Vue, although I'm unsure.
For reference I want the output to actually look like:
<component :is="component.component_type" name='test' value='some value' ></component>
How can I go about passing in these properties? Ideally I'd like these to be tied to data / props of the parent as I'm showing so that they can easily be changed in database and the UI will change accordingly.
At worst I will generate it all on server side, but I'd rather do it via ajax as I'm currently trying to do.
In case anyone is wondering how to do this using Vue 2 you can just pass an object to v-bind:
<template>
<component :is="componentType" v-bind="props"></component>
</template>
<script>
export default {
data() {
return {
componentType: 'my-component',
props: {
foo: 'foofoo',
bar: 'barbar'
}
}
}
}
</script>
Following this thread, I see two options to do this.
one is to pass a single prop which is an object in itself, and pass all the relevant key values in it which can be used by the child component, something like following:
<component :is="component. component_type"
:options="component.props"
</component>
Other solution mentioned is to have a directive, where you pass the object and it will set the attributes which are keys in that object to corresponding values, you can see this in work here.
Vue.directive('props', {
priority: 3000,
bind() {
// set the last component child as the current
let comp = this.vm.$children[this.vm.$children.length - 1];
let values = null;
if(this._scope && this._scope.$eval) {
values = this._scope.$eval(this.expression);
} else {
values = this.vm.$eval(this.expression);
}
if(typeof values !== 'object' || values instanceof Array) {
values = { data: values };
}
// apply properties to component data
for(let key in values) {
if(values.hasOwnProperty(key)) {
let hkey = this.hyphenate(key);
let val = values[key];
if(typeof val === 'string') {
comp.$options.el.setAttribute(hkey, values[key]);
} else {
comp.$options.el.setAttribute(':' + hkey, values[key]);
}
}
}
console.log(comp.$options.el.outerHTML);
comp._initState();
},
/*
* Hyphenate a camelCase string.
*/
hyphenate(str) {
let hyphenateRE = /([a-z\d])([A-Z])/g;
return str.replace(hyphenateRE, '$1-$2').toLowerCase();
}
});
and use it like this:
<div class="app">
<component is="component.component_type" v-props="component.props"></component>
</div>
As far as I know, there is no rest props (spread props) syntax in Vue.js template.
A possible solution is render functions. You have full power of JavaScript when using render functions, so you can do something like this:
render (h) {
return h('foo', {
props: {
...yourData
}
})
}
I created a simple example here: http://codepen.io/CodinCat/pen/bgoVrw?editors=1010
both component type (like foo) and props can be dynamic (but you still need to declare all the possible fields of prop (like a, b and c) in your child components)
There is another solution is JSX.
Use the JSX babel plugin: https://github.com/vuejs/babel-plugin-transform-vue-jsx
then you can use the spread props syntax:
return <foo {...{yourData}}></foo>

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)'