Vue JS focus next input on enter - javascript

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() />

Related

How to limit multiple input fields (which use v-model) to only accept numbers in Vue.js (without repeating code for every input field)?

I have multiple input fields and I want to limit them to accept numbers only in Vue.js.
I want do disable user from typing any characters except digits from 0-9.
I already did that successfully by doing this(this solution copy-paste proof):
Code in Vue.js template:
<input type="text" name="priceMax" class="input" #input="correctNumberInput" />
Method that removes everything except numbers:
correctNumberInput: function(event){
event.target.value = event.target.value.replace(/[^0-9]/g, "");
}
This worked perfectly fine on multiple fields.
Here comes the problem: For different reason, I needed to use v-model on these input fields. After adding v-model my method doesn't work anymore. I guess it's because v-model also uses input event under the hood. So only adding "v-model", stops it from working:
<input type="text" name="priceMax" class="input" #input="correctNumberInput" v-model="priceMax" />
I have few possible solutions in mind, but all of them include a lot of repeated code.
For example, I could add watchers for every input field, but that would be a lot of repeated code (because I would need to do it for every input field). I have 5 input fields, so basically I would need to write 5 almost identical watchers. I would like to avoid that if that is possible... For example:
watch:{
number(){
this.priceMax = this.priceMax.replace(/[^0-9]/g, "");
}
}
Is there any way I can solve it and make it as simple as my solution was without repeating code? It would be nice to also have solution that is copy-paste proof. All suggestions are welcome! Thanks in advance!
I've tried to test some code. Here what I have (link to the example):
<template>
<div>
<div>
<input
type="text"
name="priceMin"
class="input"
v-model="priceMin"
#input="correctNumberInput"
>
<label v-html="priceMin"></label>
</div>
<div>
<input
type="text"
name="priceMax"
class="input"
v-model="priceMax"
#input="correctNumberInput"
>
<label v-html="priceMax"></label>
</div>
</div>
</template>
<script>
export default {
name: "MyInput",
data: () => {
return {
priceMin: "",
priceMax: ""
};
},
methods: {
correctNumberInput: function(event, data) {
const name = event.target.name;
let value = String(this[name]).replace(/[^0-9]/g, "");
if (value) {
this[name] = parseInt(value, 10);
} else {
this[name] = "";
}
}
}
};
</script>
<style scoped>
input {
border: 1px solid black;
}
</style>
This is the code:
correctNumberInput: function(event, data) {
const name = event.target.name;
let value = String(this[name]).replace(/[^0-9]/g, "");
if (value) {
this[name] = parseInt(value, 10);
} else {
this[name] = "";
}
}
So I used your function, but I am not changing the event.target.value, I am changing the data. So I need to know the name of that data, that's why I use name attribute from input fields (const name = event.target.name;)
Update
If we have input type=number, then it has strange (empty) value inside #input callback. So it seems, better use keyboard filter (example here):
The main idea to have keyboard filter:
filterDigitKeys: function(event) {
const code = window.Event ? event.which : event.keyCode;
const isSpecial =
code === 37 ||
code === 39 ||
code === 8 ||
code === 46 ||
code === 35 ||
code === 36;
const isDigit = code >= 48 && code <= 57;
const isKeypad = code >= 96 && code <= 105;
if (!isSpecial && !isDigit && !isKeypad) {
// if not number or special (arrows, delete, home, end)
event.preventDefault();
return false;
}
}
And attach it to inputs:
<input type="number" min="0" name="numberInput" class="input"
v-model.number="numberInput" #keydown="filterDigitKeys">
Note: if we keep only #keydown handler, then we will not filter text insert into our inputs (but ctrl+v is not working anyway, only by mouse).
Maybe you can try this:
<input type="number" name="priceMax" class="input" #input="correctNumberInput" v-model.number="priceMax" />
From that site: click.

event prevent default not working within VueJS instance

I'm trying to disable a form submission when the enter key is pressed. The approaches I've tried are listed below with the code and example demo.
EXAMPLE OF PROBLEM HERE
Desired outcome:
Focus on the input, press down -> down -> enter and it should log the index of the record you have selected and stop there.
What's actually happening:
It logs as expected, but then reloads the page immediately as the form submits.
HTML
<form action="/some-action" #submit.stop.prevent="prevent">
<div class="auto-complete" v-cloak>
<div class="ico-input">
<input type="text" name="search" placeholder="Enter text" #keyup.prevent="handleKeypress">
</div>
<ul class="raw auto-complete-results">
<li v-for="r in results" #click="loadSelection($index)" v-bind:class="{'selected': selectedIndex == $index}"><span>{{ r.name }}</span></li>
</ul>
</div>
</form>
JS
var autocomplete = new Vue({
el: '.auto-complete',
data: {
results: [{name: 'swimming1'}, {name: 'swimming2'}, {name: 'swimming3'}, {name: 'swimming4'}, {name: 'swimming5'}, ],
selectedIndex: -1,
},
methods: {
handleKeypress: function(event) {
event.preventDefault();
event.stopPropagation();
var key = event.which;
if ([38, 40].indexOf(key) > -1) //handle up down arrows.
this.arrowNavigation(key);
else if (key == 13) //handle enter keypress
this.loadSelection(this.selectedIndex);
return false;
},
arrowNavigation: function(key, target) {
if (key == 38) //up
this.selectedIndex = this.selectedIndex - 1 < 0 ? 0 : this.selectedIndex - 1;
if (key == 40) //down
this.selectedIndex = (this.selectedIndex + 1) > (this.results.length - 1) ? 0 : this.selectedIndex + 1;
},
loadSelection: function(index) {
if (index < 0 || index > this.results.length)
return false;
var selection = this.results[index];
console.log("loading selection", index,selection);
},
prevent: function(event) {
event.preventDefault();
event.stopPropagation();
return false;
},
}
})
I've tried various syntax approaches on both form/input (switching submit for keyup on the input)
v-on:submit="prevent"
#submit
#submit.stop
#submit.prevent
#submit.stop.prevent="prevent"
I've also tried calling the following from with in the 2 event handlers aswell as returning false from them both.
event.preventDefault()
event.stopPropagation()
The form still triggers a page reload no matter what I try. I can't see anything obviously wrong so I turn to stackoverflow to guide my eyes.
Thanks
This Answer to another question suggests that forms with a single input element always get submitted, not matter what you do.
And indeed adding another input (and hiding it) helped.
https://jsfiddle.net/Linusborg/Lbq7hf1v/1/
<div class="ico-input">
<input type="text" name="search" placeholder="Enter text" #keyup.prevent="handleKeypress">
<input type="text" hidden style="display:none;">
</div>
Browsers are stupid.
Note that, anything outside of the el that you use to reference your Vue instance will not be recognized by vue no matter how correctly you define your event modifiers on your elements.
In your example you are referring to your Vue instance through the .auto-complete element which is a child of the form element which the submit event is bound to by default. Any event modifiers added to your form will never take effect. Try wrapping all your HTML in a div and reference vue through that, like so:
HTML
<div id="app">
<!-- HTML/Vue Directives/Mustache here -->
<form action="/some-action" #submit.stop.prevent="prevent">
... rest of your code ...
</form>
</div>
JS:
var autocomplete = new Vue({
el: '#app',
data: {
... rest of your js code ...
})
Also Vue devtools can be very useful when debugging such situations.
Try this!
<form #submit.prevent>
Works for Vue2
Try #keydown.enter.prevent
I was having the same problem but I was using keyup
<input type="text" #keydown.enter.prevent="doSomething" />
You don't have to have a method either if you just want to stop the form from submitting just put #keydown.enter.prevent without a method.
<input type="text" #keydown.enter.prevent />

DataPicker not getting binded to textbox ? fiddle provided

Well in other cases i will get datepicker binded to my textbox which will be straight forward but not in this case .
Fiddle link : http://jsfiddle.net/JL26Z/1/ .. while to setup perfect seanrio i tried but unable to bind datepicker to textboxes . except that everything is in place
My code :
**<script id="Customisation" type="text/html">** // here i need to have text/html
<table style="width:1100px;height:40px;" align="center" >
<tr>
<input style="width:125px;height:auto;" class="txtBoxEffectiveDate" type="text" id="txtEffective" data-bind="" />
</tr>
</script>
The above code is used for my dynamic generation of same thing n no of time when i click each time on a button . So above thing is a TEMPLATE sort of thing .
My knockout code :
<div data-bind="template:{name:'Customisation', foreach:CustomisationList},visible:isVisible"></div>
<button data-bind="click:$root.CustomisatioAdd" >add </button>
I tried same old way to bind it with datepicker
$('#txtEffective').datepicker(); // in document.ready i placed
Actually to test this i created a textbox with some id outside script with text/html and binded datepicker to it and It is working fine sadly its not working for the textbox inside text/html and i want to work at any cost.
PS: well i haven't posted my view model as it is not required in this issue based senario
View model added with Js
var paymentsModel = function ()
{
function Customisation()
{
var self = this;
}
var self = this;
self.isVisible = ko.observable(false);
self.CustomisationList = ko.observableArray([new Customisation()]);
self.CustomisationRemove = function () {
self.CustomisationList.remove(this);
};
self.CustomisatioAdd = function () {
if (self.isVisible() === false)
{
self.isVisible(true);
}
else
{
self.CustomisationList.push(new Customisation());
}
};
}
$(document).ready(function()
{
$('#txtEffective').datepicker();
ko.applyBindings(new paymentsModel());
});
Any possible work around is appreciated
Regards
The best way I've found to do this is create a simple bindingHandler.
This is adapted from code I have locally, you may need to tweak it...
** code removed, see below **
Then update your template:
** code removed, see below **
By using a bindingHandler you don't need to try to hook this up later, it's done by knockout when it databinds.
Hope this is helpful.
EDIT
I created a fiddle, because I did indeed need to tweak the date picker binding quite a lot. Here's a link to the Fiddle, and here's the code with some notes. First up, the HTML:
<form id="employeeForm" name="employeeForm" method="POST">
<script id="PhoneTemplate" type="text/html">
<div>
<span>
<label>Country Code:</label>
<input type="text" data-bind="value: countryCode" />
</span>
<span><br/>
<label>Date:</label>
<input type="text" data-bind="datepicker: date" />
</span>
<span>
<label>Phone Number:</label>
<input type="text" data-bind="value: phoneNumber" />
</span>
<input type="button" value="Remove" data-bind="click: $parent.remove" />
</div>
</script>
<div>
<h2>Employee Phone Number</h2>
<div data-bind="template:{name:'PhoneTemplate', foreach:PhoneList}">
</div>
<div>
<input type="button" value="Add Another" data-bind="click: add" />
</div>
</div>
</form>
Note I removed the id=... from in your template; because your template repeats per phone number, and ids must be unique to be meaningful. Also, I removed the datepicker: binding from the country code and phone number elements, and added it only to the date field. Also - the syntax changed to "datepicker: ". If you need to specify date picker options, you would do it like this:
<input type="text" data-bind="datepicker: myObservable, datepickerOptions: { optionName: optionValue }" />
Where optionName and optionValue would come from the jQueryUI documentation for datepicker.
Now for the code and some notes:
// Adapted from this answer:
// https://stackoverflow.com/a/6613255/1634810
ko.bindingHandlers.datepicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {},
observable = valueAccessor(),
$el = $(element);
// Adapted from this answer:
// https://stackoverflow.com/a/8147201/1634810
options.onSelect = function () {
if (ko.isObservable(observable)) {
observable($el.datepicker('getDate'));
}
};
$el.datepicker(options);
// set the initial value
var value = ko.unwrap(valueAccessor());
if (value) {
$el.datepicker("setDate", value);
}
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$el.datepicker("destroy");
});
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()),
$el = $(element);
//handle date data coming via json from Microsoft
if (String(value).indexOf('/Date(') === 0) {
value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
}
var current = $el.datepicker("getDate");
if (value - current !== 0) {
$el.datepicker("setDate", value);
}
}
};
function Phone() {
var self = this;
self.countryCode = ko.observable('');
self.date = ko.observable('');
self.phoneNumber = ko.observable('');
}
function PhoneViewModel() {
var self = this;
self.PhoneList = ko.observableArray([new Phone()]);
self.remove = function () {
self.PhoneList.remove(this);
};
self.add = function () {
self.PhoneList.push(new Phone());
};
}
var phoneModel = new PhoneViewModel();
ko.applyBindings(phoneModel);
Note the very updated binding handler which was adapted from this answer for the binding, and this answer for handling onSelect.
I also included countryCode, date, and phoneNumber observables inside your Phone() object, and turned your model into a global variable phoneModel. From a debugger window (F12 in Chrome) you can type something like:
phoneModel.PhoneList()[0].date()
This will show you the current value of the date.
I notice that your form is set up to post somewhere. I would recommend instead that you add a click handler to a "Submit" button and post the values from your phoneModel using ajax.
Hope this edit helps.
Dynamic entities need to have datepicker applied after they are created. To do this I'd use an on-click function somewhere along the lines of
HTML
<!-- Note the id added here -->
<button data-bind="click:$root.CustomisatioAdd" id="addForm" >add </button>
<script>
$(document).on('click', '#addForm', function(){
$('[id$="txtEffective"]').datepicker();
});
</script>

Angularjs form validation order

I have a simple html form containing regular text input. ng-minlength, ng-maxlength and ng-pattern angular built-in form input directives are set on the input.
Problem: ng-pattern check is applied before the length check by ng-minlength and ng-maxlength.
Question: how can I change the default check order: i.e. first check for the length, then apply pattern check?
Example:
<body ng-app>
<div>
<form name="myForm">
Name: <input name="name" type="text" ng-model="name" ng-minlength="3" ng-maxlength="16" ng-pattern="/^\w+$/"/>
<div ng-show="myForm.name.$dirty && myForm.name.$invalid">
<span ng-show="myForm.name.$error.pattern">Pattern error</span>
<span ng-show="myForm.name.$error.minlength || myForm.name.$error.maxlength">Length error</span>
</div>
<br/>
<input type="submit" value="Submit">
</form>
</div>
</body>
Current behavior:
enter "#" - see "Pattern error"
enter "###" - see "Pattern error"
Desired behavior:
enter "#" - see "Length error"
enter "###" - see "Pattern error"
FYI, related jsfiddle.
Thanks in advance.
Write your own directive:
var mod = angular.module("myApp", []);
mod.directive("nameValidation", function () {
return {
restrict: "A",
require: "ngModel",
link: function (scope, element, attrs, ngModelCtrl) {
var validate = function (value) {
var minLen = parseInt(attrs.myMinlength, 10),
maxLen = parseInt(attrs.myMaxlength, 10),
pattern = attrs.myPattern,
match = pattern.match(/^\/(.*)\/([gim]*)$/),
lenErr = false;
if (match) {
pattern = new RegExp(match[1], match[2]);
}
if (!ngModelCtrl.$isEmpty(value)) {
ngModelCtrl.$setValidity("pattern", true);
if ((minLen && value.length < minLen) || (maxLen && value.length > maxLen)) {
ngModelCtrl.$setValidity("length", false);
lenErr = true;
}
else {
ngModelCtrl.$setValidity("length", true);
lenErr = false;
}
if (!lenErr) {
if (match && !pattern.test(value)) {
ngModelCtrl.$setValidity("pattern", false);
}
else {
ngModelCtrl.$setValidity("pattern", true);
}
}
}
else {
ngModelCtrl.$setValidity("length", true);
ngModelCtrl.$setValidity("pattern", true);
}
}
ngModelCtrl.$parsers.push(validate);
ngModelCtrl.$formatters.push(validate);
}
}
});
Then in your HTML, include the app and use the directive:
<body ng-app="myApp">
<div>
<form name="myForm">
Name: <input name="name" type="text" ng-model="name" name-validation="" my-minlength="3" my-maxlength="16" my-pattern="/^\w+$/"/>
<div ng-show="myForm.name.$dirty && myForm.name.$invalid">
<span ng-show="myForm.name.$error.pattern">Pattern error</span>
<span ng-show="myForm.name.$error.length">Length error</span>
</div>
<br/>
<input type="submit" value="Submit">
</form>
</div>
</body>
The directive uses my-minlength, my-maxlength, and my-pattern for the three values. If length fails, that will trip first. If not, then pattern will show as error if it doesn't match. Consider renaming this directive if you want to use it other places besides name as minlength, maxlength, and pattern can be passed to it via attributes. If they are left off, they will be ignored.
See jsfiddle: http://jsfiddle.net/4zpxk/6/
I searched in angular code why this behavior. Then in the function 'textInputType' that it's the specific function that handles text inputs for the angular 'input' directive I found this at the end of this function, where we can see three blocks of code.
// pattern validator
if (pattern){
//validator logic
}
// min length validator
if (attr.ngMinlength) {
//validator logic
}
// max length validator
if (attr.ngMaxlength) {
//validator logic
}
So, no matter if you change the declaration order of your ng-* attributes in the html input element you will always get same result but if you change the order of the blocks, I mean, put the min length validator block before pattern validator block you will have the result that you expect.
This is a solution for your problem but you have to make a litte change in angular code and I don't know if you really like this. But you got a very common situation where order of the declaration of validation concepts matters, so, something more must be done to handle this. Thanks
You cannot change the default check order unfortunately.
One solution is to write a custom validator, not that difficult. Based on this answer, I came up with this code (fiddle)
Usage: There is an array of validation functions in the scope, they get passed to our custom directive "validators" as:
<input name="name" type="text" ng-model="name" validators="nameValidators"/>
A validator function would look like (e.g. for the minlength constraint):
function minlength(value, ngModel) {
if( value == null || value == "" || value.length >= 3 ) {
ngModel.$setValidity('minlength', true);
return value;
}
else {
ngModel.$setValidity('minlength', false);
return;
}
}
Important points are: it takes the value and the ngModel as arguments, performs the test (here value.length >= 3) and calls ngModel.$setValidity() as appropriate.
The directive registers the given functions with ngModel.$parsers:
app.directive("validators", function($parse) {
return {
restrict: "A",
require: "ngModel",
link: function(scope, el, attrs, ngModel) {
var getter = $parse(attrs.validators),
validators = getter(scope),
i;
for( i=0; i < validators.length; i++ ) {
ngModel.$parsers.push((function(index) {
return function(value) {
return validators[index](value, ngModel);
};
})(i));
}
}
};
});
Many details can be tweaked and improved, but the outline works (again link to fiddle). Now the order of validation is explicitly set by the order of the validator functions in the nameValidators array.
If you use ng-messages you should be able to set the order via the order of ng-message elements, e.g:
<div ng-messages="field.$error">
<ul class="validation-errors">
<li ng-message="required">This has the highest prio</li>
<li ng-message="min">Second in command</li>
<li ng-message="max">I'm last</li>
</ul>
</div>
Also the docs on this: https://docs.angularjs.org/api/ngMessages/directive/ngMessages
i just changed the order of your directives, pattern first
<input name="name" type="text" ng-model="name" ng-pattern="/^\w+$/" ng-minlength="3" ng-maxlength="16"/>
EDIT: uuum, tested your fiddel without changes and it shows your desired behavior ...
directives are compiled by priority, bbut i don't know how to set angulars directives priority ... sorry, should have tested this first

Passing jQuery objects to a function

Here is the working demo of what I want to achieve. Just enter some value in input and you might get what I want to achieve. (Yes, I got it working but stay on..)
But it fails when multiple keys are pressed together.
What I am trying :
I have screen which contains few enabled and few disabled input elements. Whenever user updates any value in editable input element, I want to update disabled input which had same value with user updated value.
HTML :
<input value="foo" /> // When User updates this
<br/>
<input value="bar">
<br/>
<input value="Hello">
<br/>
<input value="World">
<br/>
<input value="foo" disabled> // this should be updated
<br/>
<input value="bar" disabled>
<br/>
<input value="foo" disabled> // and this also
<br/>
<input value="bar" disabled>
<br/>
<input value="Happy Ending!">
<br/>
I tried this which I think will save me from multiple_clicks_at_a_time
JS:
$(":input:not(:disabled)").keyup(function () {
// Get user entered value
var val = this.value;
// Find associated inputs which should be updated with new value
siblings = $(this).data("siblings");
$(siblings).each(function () {
// Update each input with new value
this.value = val;
});
});
$(function () {
$(":input:not(:disabled)").each(function () {
// Find inputs which should be updated with this change in this input
siblings = $(":input:disabled[value=" + this.value + "]");
// add them to data attribute
$(this).data("siblings", siblings);
});
});
But I am not able to pass the selectors to keyup function and invoke .each on it.
PS:
My previous completely different try, working with single_click_at_a_time but I felt that I am unnecessarily traversing the DOM again and again so dropped this
$(":input").keypress(function () {
$(this).data("oldVal", this.value);
});
$(":input").keyup(function () {
var oldVal = $(this).data("oldVal");
$(this).data("newVal", this.value);
var newVal = $(this).data("newVal");
$("input:disabled").each(function () {
if (this.value == oldVal) this.value = newVal;
});
});
I would group those inputs first and bind a handler for enabled elements to apply to the group. See below,
var grpInp = {};
$(":input").each(function () {
if (grpInp.hasOwnProperty(this.value)) {
grpInp[this.value] = grpInp[this.value].add($(this));
} else {
grpInp[this.value] = $(this);
}
});
$.each(grpInp, function (i, el) {
el.filter(':enabled').keyup(function () {
el.val(this.value);
});
});
DEMO: http://jsfiddle.net/fjtFA/9/
The above approach basically groups input element with same value, then filters them based on :enabled and bind a handler to apply it to the group.
// Find associated inputs which should be updated with new value
siblings = $(this).data("siblings", siblings);
No. The .data method called with two arguments does not get, but set the data (and returns the current selection). Also, you should make your variables local:
var siblings = $(this).data("siblings");
Working demo

Categories