I have a form where user can fill an email address and click a plus button against it to create a new one. These input boxes are generated by iterating over an array. When user clicks on the + icon, a new entry is pushed to this array.
Now the new text box is generating fine but I want the cursor to be focused in this one.
as #ramakant-mishra told you must use this.$refs, but you need to add ref attribute dynamically on your input element also. let me show you:
new Vue({
el: '#app',
data: {
emails:[]
},
methods: {
add: function (e) {
let j = this.emails.push("")
this.$nextTick(() => {
this.$refs[`email${j - 1}`][0].focus()
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(email, i) in emails" :key="i">
<input v-model="email" :ref="`email${i}`" />
</div>
<button #click="add">add</button>
</div>
just don't forget to use $nextTick because the new element is not rendered yet on template
They key is to set ref on all your inputs to the same string like this:
<input type="text" ref="myInputs"/>
Then you will have access to an array called this.$refs.myInputs inside an event handler.
new Vue({
el: "#app",
data() {
return {
emails: []
};
},
methods: {
addEmail() {
this.emails.push('whatever');
this.$nextTick(() => {
const lastIdx = this.emails.length - 1;
this.$refs.myInputs[lastIdx].focus();
});
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<input type="button" #click="addEmail()" value="Add Email"/>
<div v-for="(email, index) in emails" :key="index">
<input ref="myInputs" type="text" />
</div>
</div>
Note that below you must put the call to focus() inside a nextTick() in order to give Vue a chance to actually render the email you just added to the list.
Related
I am developing a Vuejs application within which I have a field. For this field, users can provide the values and this field expands dynamically based on the user-provided values.
The field name is extensions, initially an Add Extension button will be displayed. With on click of the button, a bootstrap modal will be displayed which has 3 fields: namespace (text), localname (text), datatype(dropdown: string/complex). If the datatype is string then a simple text field will be displayed. However, if the datatype is complex then another button should be displayed and with on click of the button again the same modal is displayed with fields and the process continues. So the created JSON based on this will expand horizontally and vertically.
I am able to complete the first iteration and display the elements to users on the front end. However, for further iteration, I am not understanding how to achieve it using the recursive approach. Since I don't know how many times users may create the extensions I need to have an approach that dynamically does this.
Can someone please help me how to create and display the JSONarray using Vuejs which expands horizontally and vertically?
Following is the code I have so far:
<template>
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
<span>Extensions</span>
<button class="form-control" #click="createExtensions()">
Add Another
</button>
</div>
</div>
<div v-for="extension in extensionList" :key="extension.ID" class="form-inline">
<span>{{ extension.namespace+ ":"+extension.localName }}</span>
<input v-if="extension.dataType == 'string'" type="text" #input="AddExtensionText($event,extension.ID)">
<button v-if="extension.dataType == 'complex'" #click="AddComplextExtension(extension.ID)">
Add another
</button>
</div>
<b-modal
id="Extension"
title="Add Another Element"
size="lg"
width="100%"
:visible="showModal"
>
<b-form id="AddExtension" #submit.prevent="submitExtension">
<div class="form-group">
<label for="message-text" class="col-form-label">Namespace URI:</label>
<input
v-model="extension.namespace"
type="text"
class="form-control"
required
>
</div>
<div class="form-group">
<label for="message-text" class="col-form-label">Local Name:</label>
<input
v-model="extension.localName"
type="text"
class="form-control"
required
>
</div>
<div class="form-group">
<label for="AddExtensionDataType" class="col-form-label">Data Type:</label>
<b-form-select v-model="extension.dataType" class="form-control">
<b-form-select-option value="string">
String
</b-form-select-option>
<b-form-select-option value="complex">
Complex
</b-form-select-option>
</b-form-select>
</div>
</b-form>
<template #modal-footer="{ cancel }">
<b-btn #click="cancel">
Cancel
</b-btn>
<b-btn variant="primary" type="submit" form="AddExtension">
OK
</b-btn>
</template>
</b-modal>
</div>
</template>
<script>
export default {
data () {
return {
extensionList: [],
extensionCount: 0,
extension: {
namespace: '',
localName: '',
dataType: 'string'
},
showModal: false
}
},
methods: {
// Method to create extensions and add
createExtensions () {
this.showModal = true
},
// Function to submit the each extension
submitExtension () {
this.showModal = false
const extensionObj = {}
extensionObj.ID = this.extensionCount
extensionObj.namespace = this.extension.namespace
extensionObj.localName = this.extension.localName
extensionObj.dataType = this.extension.dataType
this.extensionList.push(extensionObj)
this.extensionCount++
},
// On addition of the input field value update List
AddExtensionText (event, extensionID) {
const extension = this.extensionList.filter(ex => ex.ID === extensionID)[0]
if (typeof extension !== 'undefined') {
extension.text = (typeof event.target.value !== 'undefined') ? event.target.value : ''
}
},
// Add complex extension
AddComplextExtension (extensionID) {
this.showModal = true
}
}
}
</script>
<style>
</style>
This is the initial field I have:
This is what I want to achieve where everything is created dynamically and JSON expands both horizontally and vertically:
Can someone please let me know how to create such dynamic JSON using Vuejs and display the same in the frontend.
To display data recursively, you need to use recursive components.
Abstract your v-for code into another component file (let's call it NodeComponent.vue). Pass your extensionList to this component, then inside this component, add another NodeComponent for each extension which has type complex.
Since your extension would be another array if it is complex, you can pass it directly into this NodeComponent as a prop and let recursion work its magic.
NodeComponent.vue
<template>
<div>
<div
v-for="extension in extensionList"
:key="extension.ID"
class="form-inline"
>
<span>{{ extension.namespace + ":" + extension.localName }}</span>
<input
v-if="extension.dataType == 'string'"
type="text"
#input="$emit('AddExtensionText', {$event, id: extension.ID}"
/>
<NodeComponent v-if="extention.dataType == 'complex'" :extentionList="extension" #AddExtensionText="AddExtensionText($event)"/>
<button
v-if="extension.dataType == 'complex'"
#click="AddComplextExtension(extension.ID)"
>
Add another
</button>
</div>
</div>
</template>
<script>
export default {
props: {
extensionList: Array,
extension: Object,
},
methods: {
AddComplextExtension(extensionID) {
// Emit event on root to show modal, or use this.$bvModal.show('modal-id') or create a dynamic modal, see: https://bootstrap-vue.org/docs/components/modal#message-box-advanced-usage
}
AddExtensionText({ value, id }) {
const i = this.extensionList.findIndex((el) => el.ID === id);
this.$set(extensionList, i, value);
}
}
};
</script>
Note that I emit a custom event from child NodeComponents on changing input text so that the parent can make this change in its extensionList array, using this.$set to maintain reactivity.
EDIT: If you want to add new Node components:
You need to have a parent component that holds the first NodeComponent in it. In here you'll define the modal (if you define it inside NodeComponent, you'll have a separate modal reference for each NodeComponent. Judging from your code you're probably using Bootstrap-Vue, it injects modals lazily when shown, so I don't think this will affect your performance too much, but it still doesn't feel like good code.). You need to emit event on root to show the modal. You need to send the extensionList as payload with this event like this: this.$root.emit('showModal', extensionList). In you parent component you can listen to the event and show the modal. Now inside your submitExtension function, you can use this extensionList and push a new object to it. The corresponding NodeComponent will update itself since arrays are passed by reference.
this.$root.on('showModal`, (extensionList) => {
this.editExtensionList = extensionList;
showModal = true;
}
submitExtension() {
this.showModal = false
const extensionObj = {}
extensionObj.ID = this.extensionCount
extensionObj.namespace = this.extension.namespace
extensionObj.localName = this.extension.localName
extensionObj.dataType = this.extension.dataType
this.editExtensionList.push(extensionObj)
this.extensionCount++
}
All being said, at this point it might be worthwhile to invest in implementing a VueX store where you have a global extentionList and define mutations to it.
I am using a color picker component external from vue2. While it renders nicely and seem to function well with basic usage, I am not able to detect changes to the input value within the vue component. I tried adding #change to the component instance, although it never seems to fire. Any idea why this is? Note, the value change color hex value but never databinding in input
the problem is item.color never change.. the new value from input is assigned to input value from script external colorpicker but never to item.color (never binding) and cant catch de new value
i don't find action them catch them
I use #change
I use #focus
<input
class="inp input_flexible"
:data-did="'A' + (index + 1) + '-colorPicker'"
name="color"
:id="'color_' + item.TIER_ID"
autocomplete="off"
type="text"
v-model="item.color"
v-on:input="cambiarcolor($event)"
#click="metodocolor(index, $event)"
#blur="metodoblur(index,$event)"
#change="cambiarcolor($event)"
/>
You could use watch to do something when item.color (v-model binding) change.
new Vue({
el: '#app',
data() {
return {
item: {
color: ""
},
doSomething: ""
}
},
watch: {
"item.color"(newValue) {
this.doSomething = newValue;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="radio" value="#FFFFFF" v-model="item.color">
<label>White</label>
<br>
<input type="radio" value="#000000" v-model="item.color">
<label>Black</label>
<br>
<span> {{ doSomething }} </span>
</div>
This question already has answers here:
Vuejs and Vue.set(), update array
(5 answers)
Closed 2 years ago.
I'm working on an e-commerce website, I want to have a button to add items to cart.
When I add a button, value in nbrSeats (my list of values) change (in data), but the input field shows another value.
I put a part of the code here:
https://codepen.io/martinrougeron2/pen/MWKdJBb
<!-- Use preprocessors via the lang attribute! e.g. <template lang="pug"> -->
<template>
<div id="app">
<button #click="create_list()">Create list</button>
<div v-for="(i, index) in nbrSeats" :key="index">
<input
type="number"
max="10"
min="1"
v-model="nbrSeats[index]"
style="width: 60px"
label="Nb. places"
>
</input>
<button #click="nbrSeats[index]++">Add</button>
</div>
<button #click="show()">Show me values</button>
</div>
</template>
<script>
export default {
data() {
return {
nbrSeats: [],
message: 'Welcome to Vue!'
};
},
methods: {
doSomething() {
alert('Hello!');
},
create_list() {
for (var i = 0; i < 10; i++) {
this.nbrSeats.push(1)
}
},
show() {
console.log(this.nbrSeats)
},
}
};
</script>
It is because Vue.js is not picking on changes when you mutate directly in the array.
Instead use Vue.set.
Change your callback:
<button #click="addSeat(index)">Add</button>
And then add this method:
addSeat(index) {
this.$set(this.nbrSeats, index, this.nbrSeats[index] + 1);
}
Vuejs and Vue.set(), update array
https://v2.vuejs.org/v2/guide/reactivity.html#For-Arrays
I'm building an autocomplete feature Basically, what I DON'T want is to bind the element like this:
v-model="input"
Binding on the element with the v-model or v-bind, gives my input element a blank value.
Instead, I'd like my element to be able to pick up an old value or a value from the database as seen in the code below. I'd only like to bind the element's value to my variable named "input" after the page has loaded with all this data from the DB. The code below works great, but I have to use document.getElementById to update my element with the new value.
<div id="spvs" class="uk-form-controls">
<input v-on:input="input = $event.target.value" v-on:keyup="getCompanies" name="company" id="company" class="uk-input {{$errors->has('company') ? ' uk-form-danger' : ''}}" placeholder="company name"
value="{{ old('company') || $errors->has('company')
? old('company')
: $property->getCompanyName()
}}">
<div v-if="spvs.length > 0" class="tm-autocomplete-box">
<ul class="uk-list uk-list-striped tm-autocomplete-list">
<li v-for="(spv, key) in spvs" #click="complete(key)"><span uk-icon="plus-circle" class="uk-margin-right"></span> #{{ spv.name }}</li>
</ul>
</div>
</div>
Ideally, I'd bind the element value to my 'input' variable when the user clicks one of the autocomplete items. Which runs a function called 'complete'.
methods:{
complete: function(key){
this.input = this.spvs[key].name;
document.getElementById('company').value = this.input;
this.spvs = '';
},
So this is the line that I would like to replace with the new binding:
document.getElementById('company').value = this.input;
So you want your input to have a old value when the component loads and then you want to update the value.Still you can use v-model as below.
When component loads the input will have the old value you want to set it, and also you can update the value:
<div id="app">
<input type="text" v-model="oldValue">
</div>
new Vue({
el: "#app",
data: {
oldValue: 'value from Database'
}
})
If you want another way there it is:
<div id="app">
<input type="text" :value="oldValue" #input="changeValue">
<hr>
The value of input is: {{oldValue}}
</div>
new Vue({
el: "#app",
data: {
oldValue: 'value from Database'
},
methods: {
changeValue(newValue) {
this.oldValue = newValue.target.value
}
}
})
See in action the first example
See in action the second example
I have created an example which is similar to my current work.
var App = new Vue({
el:'#app',
data:{
demoText:'text',
sections:2,
sectionData:[0,0]
},
methods:{
changeData:function(){
for(var i=0;i<this.sections;i++){
if(isNaN(this.sectionData[i]))
this.sectionData[i]=0;
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id='app'>
<div v-for="n in sections">
<input type="text" v-model=sectionData[n-1]>
</div>
<input type="button" #click="changeData" value="click">
</div>
In this example, when I click on the button I check if data is isNaN and change it to zero if it is. But this updated data doesn't update in DOM/textbox immediately. After that, if I update in another textbox then pending update action happens.
How can I resolve this issue?
There are a couple of issues in play here.
First is that Vue cannot detect the change you are making in your click handler when you change it by index. This is a common caveat when modifying an array in Vue.
Second, there is a bug in the code where if you blank out a text box, isNaN("") will resolve to false. This is documented in a number of places. I've implemented a very naive fix for this in the code below, but you should search for a stronger solution.
var App = new Vue({
el:'#app',
data:{
demoText:'text',
sections:2,
sectionData:[0,0]
},
methods:{
changeData:function(){
this.sectionData = this.sectionData.map(d => isNaN(parseInt(d,10)) ? 0 : d)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id='app'>
<div v-for="n in sections">
<input type="text" v-model=sectionData[n-1]>
</div>
<input type="button" #click="changeData" value="click">
</div>
Another alternative implementation of your click handler would be
for (let i=0; i < this.sectionData.length; i++)
if (isNaN(parseInt(this.sectionData[i],10)))
this.sectionData.splice(i, 1, 0)