Background
I have a v-for loop that contains a pair of buttons and inputs. The buttons contains an #click event that is suppose to show the corresponding input.
Problem
When the buttons are clicked it shows all the input instances instead of the corresponding input to the button clicked.
<div class="buttonFor my-3" v-for="(expense,index) in expenseButton" :key="index">
<input type="submit" #click="showInput(expense)" :value="expense.expensesKey" />
<input v-show="needEdit" v-model.number="expense.subExpense" type="number" />
</div>
data() {
return {
expenseButton: [],
needEdit: false
};
}
methods: {
showInput() {
this.needEdit = !this.needEdit;
}
}
I think it's better you keep track of the chosen id. you could edit your data to receive chosen id like:
data() {
return {
expenseButton: [],
chosenExpenseId: null
};
}
your show input receives the index instead:
methods: {
showInput(index) {
this.chosenExpenseId = index;
// if want to make the button to hide input once clicked back
// this.chosenExpenseId = this.chosenExpenseId !== index ? index : null;
}
}
at your template you pass the index and validates input show condition as:
<input type="submit" #click="showInput(index)" :value="expense.expensesKey" />
<input v-show="index === chosenExpenseId" v-model.number="expense.subExpense" type="number" />
I have 2 inputs and want switch focus from first to second when user press Enter.
I tried mix jQuery with Vue becouse I can't find any function to focus on something in Vue documentation:
<input v-on:keyup.enter="$(':focus').next('input').focus()"
...>
<input ...>
But on enter I see error in console:
build.js:11079 [Vue warn]: Property or method "$" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in anonymous component - use the "name" option for better debugging messages.)warn # build.js:11079has # build.js:9011keyup # build.js:15333(anonymous function) # build.js:10111
build.js:15333 Uncaught TypeError: $ is not a function
You can try something like this:
<input v-on:keyup.enter="$event.target.nextElementSibling.focus()" type="text">
JSfiddle Example
Update
In case if the target element is inside form element and next form element is not a real sibling then you can do the following:
html
<form id="app">
<p>{{ message }}</p>
<input v-on:keyup.enter="goNext($event.target)" type="text">
<div>
<input type="text">
</div>
</form>
js
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
focusNext(elem) {
const currentIndex = Array.from(elem.form.elements).indexOf(elem);
elem.form.elements.item(
currentIndex < elem.form.elements.length - 1 ?
currentIndex + 1 :
0
).focus();
}
}
})
JSFiddle Example
Following up from #zurzui here is in my opinion a cleaner alternative using the $refs API (https://v2.vuejs.org/v2/api/#vm-refs).
Using the $refs API, can allow you to target element in a simpler fashion without traversing the DOM.
Example: https://jsfiddle.net/xjdujke7/1/
After some tests, it's working
new Vue({
el:'#app',
methods: {
nextPlease: function (event) {
document.getElementById('input2').focus();
}
}
});
<script src="https://vuejs.org/js/vue.js"></script>
<div id='app'>
<input type="text" v-on:keyup.enter="nextPlease">
<input id="input2" type="text">
</div>
directives: {
focusNextOnEnter: {
inserted: function (el,binding,vnode) {
let length = vnode.elm.length;
vnode.elm[0].focus();
let index = 0;
el.addEventListener("keyup",(ev) => {
if (ev.keyCode === 13 && index<length-1) {
index++;
vnode.elm[index].focus();
}
});
for (let i = 0;i<length-1;i++) {
vnode.elm[i].onfocus = (function(i) {
return function () {
index = i;
}
})(i);
}
}
}
}
// use it
<el-form v-focusNextOnEnter>
...
</el-form>
Try this:
<input ref="email" />
this.$refs.email.focus()
Whilst I liked the directives answer due to it working with inputs inside other elements (style wrappers and so on), I found it was a little inflexible for elements that come and go, especially if they come and go according to other fields. It also did something more.
Instead, I've put together the following two different directives. Use them in your HTML as per:
<form v-forcusNextOnEnter v-focusFirstOnLoad>
...
</form>
Define them on your Vue object (or in a mixin) with:
directives: {
focusFirstOnLoad: {
inserted(el, binding, vnode) {
vnode.elm[0].focus();
},
},
focusNextOnEnter: {
inserted(el, binding, vnode) {
el.addEventListener('keyup', (ev) => {
let index = [...vnode.elm.elements].indexOf(ev.target);
if (ev.keyCode === 13 && index < vnode.elm.length - 1) {
vnode.elm[index + 1].focus();
}
});
},
},
},
On an enter key pressed, it looks for the index of the current input in the list of inputs, verifies it can be upped, and focuses the next element.
Key differences: length and index are calculated at the time of the click, making it more suitable for field addition/removal; no extra events are needed to change a cached variable.
Downside, this will be a little slower/more intensive to run, but given it's based off UI interaction...
Vue.js's directive is good practice for this requirement.
Define a global directive:
Vue.directive('focusNextOnEnter', {
inserted: function (el, binding, vnode) {
el.addEventListener('keyup', (ev) => {
if (ev.keyCode === 13) {
if (binding.value) {
vnode.context.$refs[binding.value].focus()
return
}
if (!el.form) {
return
}
const inputElements = Array.from(el.form.querySelectorAll('input:not([disabled]):not([readonly])'))
const currentIndex = inputElements.indexOf(el)
inputElements[currentIndex < inputElements.length - 1 ? currentIndex + 1 : 0].focus()
}
})
}
})
Note: We should exclude the disabled and readonly inputs.
Usage:
<form>
<input type="text" v-focus-next-on-enter></input>
<!-- readonly or disabled inputs would be skipped -->
<input type="text" v-focus-next-on-enter readonly></input>
<input type="text" v-focus-next-on-enter disabled></input>
<!-- skip the next and focus on the specified input -->
<input type="text" v-focus-next-on-enter='`theLastInput`'></input>
<input type="text" v-focus-next-on-enter></input>
<input type="text" v-focus-next-on-enter ref="theLastInput"></input>
</form>
<input type="text" #keyup.enter="$event.target.nextElementSibling.focus() />
I have a main checkbox, clicking on which all the checkboxes are checked. For the child checkboxes, I have some different logic involving their computation from 2 different observables besides the mainCheckBox and when child checkboxes are checked, the value needs to be written to different observables.
I have created a computed observable with parameter as below:
var vm = ko.mapping.fromJS(data);
var viewModel =
function ()
{
this.self = this;
this.ExportSchemas = vm;
this.SelectAll = ko.observable(true);
this.IsChecked = function (checkboxVM) {
return ko.computed({
read: function () {
return this.SelectAll(); //problemLine
},
write: function (value) {
checkboxVM.Selected(value);
alert(checkboxVM.Selected());
},
owner: this.self
});
};
};
<input type="checkbox" data-bind="checked: SelectAll" value="None" id="mainCheckBox" name="check" />
<div data-bind="foreach: ExportSchemas">
<div data-bind="foreach: Clauses">
<input type="checkbox" value="None" data-bind="checked: $root.IsChecked($data), attr: { id: 'clause' + Id(), value: Id() }" name="check" />
</div>
</div>
The write behavior of computed observable is weird, it works on alternate clicks when the problemLine in above code is as written above. And when I use the following instead for problemLine:
return this.SelectAll;
Then the write of computed observable works properly, but the the read stops working and checking/unchecking mainCheckBox has no effect on child check boxes.
Any suggestions?
I have a button to create radio buttons like this:
<input value="↑" type="button" data-bind=" click: moveUp"/>
<input value="↓" type="button" data-bind="click: moveDown"/>
<input data-bind="value: selectedradio" />
<div data-bind="foreach: radios">
<div data-bind="attr:{id:radioid} ">
<input type="radio" name="group" data-bind="value:radioid, checked:$parent.selectedradio"/>
</div>
</div>
<div>
<input type="button" value="+" data-bind="click: addRadio"/>
</div>
when the radio buttons are created they are each assigned a unique index and i have bound the checked attribute to a property on the parent called selectedradio
I am able to modify selectedradio though an html input and the selected radio will change but If I modify the value progmmatically it will not.
I created a fiddle here: http://jsfiddle.net/8vVeU/
here is the javascript for reference:
function Radio(id){
this.radioid = ko.observable(id);
}
function ViewModel() {
var self = this;
self.selectedradio = ko.observable(0);
self.radios = ko.observableArray([new Radio(0),new Radio(1),new Radio(2),new Radio(3)]);
self.addRadio = function() {
self.radios.push(new Radio());
self.reIndex();
};
self.moveUp = function() {
self.selectedradio(self.selectedradio()-1)
self.reIndex();
};
self.moveDown = function() {
self.selectedradio(self.selectedradio()+1)
self.reIndex();
};
self.reIndex = function() {
$.each(self.radios(), function(i, r) {
r.radioid(i);
});
}
}
ko.applyBindings(new ViewModel());
How can I resolve this issue?
In the case of radio buttons the checked binding by default compares the input's value attribute - which is a string - to the model property.
So you need to store strings in your selectedradio :
self.moveUp = function(r) {
r=parseInt(r,10);
self.selectedradio((parseInt(self.selectedradio())-1).toString())
self.reIndex();
};
self.moveDown = function(r) {
r=parseInt(r,10);
self.selectedradio((parseInt(self.selectedradio())+1).toString())
self.reIndex();
};
It works with the textbox because it will fill in the selectedradio with the entered string.
Demo JSFiddle.
Or you can change the default behavior and you can tell KO to use a different property as the checkbox's value with the checkedValue option:
<input type="radio" name="group" data-bind="value:radioid,
checked:$parent.selectedradio, checkedValue: radioid"/>
In this case you don't need to alter your moveUp and moveDown methods because now the selectedradio will always contain integers however because your textbox still sets the selectedradio to string it won't work any more.
Demo JSFiddle.
I have several checkboxes and a fake submit button to make an AJAX request:
<form>
<input type="checkbox" value="1"/>
<input type="checkbox" value="2" checked="checked"/>
<input type="checkbox" value="3"/>
<input type="checkbox" value="4" checked="checked"/>
<input type="button" onclick="return mmSubmit();"/>
</form>
Within the mmSubmit() method, I would like to retrieve an array of values that have been selected. Here is what I am currently doing.
mmSubmit = function() {
var ids = [];
$('input[type=checkbox]:checked');.each(function(index) {
ids.push($(this).attr('value'));
});
// ids now equals [ 2 , 4 ] based upon the checkbox values in the HTML above
return false;
};
I'm wondering if there is a shorthand method in jQuery used to retrieve the values into an array, or if what I have is already optimal.
I think this can be accomplished with map. Try the following..
mmSubmit = function() {
var ids = $('input[type=checkbox]:checked').map(function(){
return $(this).val();
}).get();
// ids now equals [ 2 , 4 ] based upon the checkbox values in the HTML above
return false;
};
Take a look at: jQuery Traversing/Map
Well you can use .val() instead of .attr('value').
$.serializeArray() may also do what you want (http://docs.jquery.com/Ajax/serializeArray).
It's needs some optimization, buts generally it is right way. My variant:
mmSubmit = function () {
var ids = [];
$('input[type=checkbox]').each(function () {
if (this.checked) {
ids[ids.length] = this.value;
}
});
return ids;
};
It's little faster.