How do I simplify this jquery script? - javascript

I have an ASP.NET web forms project that I am trying to implement auto-tabbing in. I'm new to jquery, but I found a code snippet online to do auto-tabbing, and I want to use it to autotab multiple groups of textboxes.
For example:
Textbox1 -> Textbox2 -> Textbox3
Textbox4 -> Textbox5 -> Textbox6
But not:
Textbox3 -> Textbox4
Hope that makes sense. Anyway, I have the following code:
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
$(".autotab").keyup(function () {
if ($(this).attr("maxlength") == $(this).val().length) {
var index = $(".autotab").index(this);
var item = $($(".autotab")[++index]);
if (item.length > 0)
item.focus();
}
});
$(".autotab2").keyup(function () {
if ($(this).attr("maxlength") == $(this).val().length) {
var index = $(".autotab2").index(this);
var item = $($(".autotab2")[++index]);
if (item.length > 0)
item.focus();
}
});
});
</script>
<input name="tbOne" type="text" maxlength="3" id="tbOne" class="autotab" />
<input name="tbTwo" type="text" maxlength="3" id="tbTwo" class="autotab" />
<input name="tbThree" type="text" maxlength="4" id="tbThree" class="autotab" />
<input name="tbFour" type="text" maxlength="3" id="tbFour" class="autotab2" />
<input name="tbFive" type="text" maxlength="3" id="tbFive" class="autotab2" />
<input name="tbSix" type="text" maxlength="4" id="tbSix" class="autotab2" />
How can I refactor the copy/pasted code into a single function?

A more general solution, that doesn't require that you use one class per group:
// loop through adjacent pairs
$.fn.eachPair = function(f) {
for(var i = 0, j = 1; j < this.length; i = j++)
f.call(this[i], this[j]);
}
$.fn.autotab = function() {
this.eachPair(function(next) {
// add an event handler to focus the next field
$(this).keyup(function() {
if($(this).attr("maxlength") == $(this).val().length)
$(next).focus();
});
});
}
$(document).ready(function () {
$(".autotab").autotab();
$(".autotab2").autotab();
});
As a side note, the $($(".autotab2")[++index]) in your code would have been better written as $(".autotab2").eq(index + 1)

You can limit the element by passing in a selector into .next() - this is assuming you have one class assigned to your inputs only and they are all siblings.
// get all inputs with class that start with autotab
$("input[class^=autotab]").keyup(function() {
var $el = $(this);
// get nextfield with same class
var nextField = $el.next('.'+$el.attr('class'));
if($el.attr("maxlength") == $el.val().length){
// if true - give next field with same class focus
$el.next().focus();
}
});​
http://jsfiddle.net/JaVaR/
Or if they aren't guaranteed siblings.. you can use .eq() to get current index and increment by one. This will get the collection of elements that match the current elements class, then get the index of the current to find the next field
$("input[class^=autotab]").keyup(function() {
var $el = $(this);
// gets all inputs with same class
var $inp = $('.'+$el.attr('class'));
// gets next input with same class
var nextField = $inp.eq($inp.index(this)+1);
if($el.attr("maxlength") == $el.val().length){
nextField.focus();
}
});​
http://jsfiddle.net/L4MEP/

Related

getElementByID.readOnly in array

Im trying to create a list of input ID's and use it in array, to make them readOnly - but the result is error -> "cannot read property 'readOnly' of null".
Can you give me a hint what I should change?
script language="javascript" type="text/javascript">
$(document).ready(function () {
$(function(){
var index, len;
$.get('/SomeList.txt', function(data){
var SomeList = data.split('\n');
for (index = 0, len = SomeList.length; index < len; index++) {
document.getElementById(SomeList[index]).readOnly = true;
}
});
});
});
</script>
and txt file contains name of input ID:
TextFieldName
TextFieldEmail
TextFieldDepartment
TextFieldOffice
Assuming you have some elements with the given IDs you must check if the element exists first before doing
document.getElementById(SomeList[index]).readOnly = true;
so replace that line with
var myElement = document.getElementById(SomeList[index]);
if(myElement == null) {
return;
}
myElement.readOnly = true;
That should work like following example where the IDs come from an array and the second one will not mach because of xxxxx so it's not readonly. But all the others are.
var dataArray = [
'TextFieldName',
'TextFieldEmailxxxxx',
'TextFieldDepartment',
'TextFieldOffice'
];
dataArray.forEach(function(id){
var myElement = document.getElementById(id);
if(myElement == null) {
return;
}
myElement.readOnly = true;
});
<input id="TextFieldName" type="text">
<input id="TextFieldEmail" type="text">
<input id="TextFieldDepartment" type="text">
<input id="TextFieldOffice" type="text">
var id_array=["email","country"];
for (i = 0; i <id_array.length; i++) {
document.getElementById(id_array[i]).readOnly = true;
}
Email: <input type="text" id="email" value="test#mail.com"><br>
Country: <input type="text" id="country" value="Norway" >
it is working fine in my case.
i think there may be whitespace in your array items because your are reading them from file.so try to trim array items.
and make sure you assign id's to input elements

Javascript - prevent onclick event from being attached to all elements with same class

I'm working on a simple form that includes an input field where the user will fill in the required amount by clicking the incrementor/decrementor. The form is created based on data pulled dynamically from the database
Below is the problematic part: html and the jquery handling it:
The incrementor, decrementor and the input field:
-
<input type="text" id="purchase_quantity" class = "purchase_quantity" min="1" max="6" delta="0" style = "width: 32px;" value="1">
+
and the jquery handling the above:
jQuery(function ($) {
$('.addItem').on('click', function () {
var inputval = $(this).siblings('.purchase_quantity').val();
var num = +inputval;
num++;
if(num>6)num=6;
console.log(num);
$(".purchase_quantity").val(num);
return false;
});
$('.removeItem').on('click', function () {
var inputval = $(this).siblings('.purchase_quantity').val();
var num = +inputval;
num--;
if(num<1)num=1;
console.log(num);
$(".purchase_quantity").val(num);
return false;
});
});
Now, what's happening is: onclick of the incrementor/decrementor (+ and -) the value on the input field changes across all the fields in the page instead of the one clicked only. Have spent quite some time on this with no success and will appreciate some help
The line
$(".purchase_quantity").val(num);
says, literally, to change the value on all the fields. Earlier you used
$(this).siblings('.purchase_quantity').val()
to get the value, so why not also use
$(this).siblings('.purchase_quantity').val(num)
to set it?
That's because siblings will get you all items on the same level.
Get the siblings of each element in the set of matched elements,
optionally filtered by a selector.
Place them in separate div elements, and adjust your setter to actually only update the siblings inside that div.
jQuery(function ($) {
$('.addItem').on('click', function () {
var inputval = $(this).siblings('.purchase_quantity').val();
var num = +inputval;
num++;
if(num>6)num=6;
console.log(num);
$(this).siblings('.purchase_quantity').val(num);
return false;
});
$('.removeItem').on('click', function () {
var inputval = $(this).siblings('.purchase_quantity').val();
var num = +inputval;
num--;
if(num<1)num=1;
console.log(num);
$(this).siblings('.purchase_quantity').val(num);
return false;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
-
<input type="text" id="purchase_quantity2" class = "purchase_quantity" min="1" max="6" delta="0" style = "width: 32px;" value="1">
+
</div>
<div>
-
<input type="text" id="purchase_quantity1" class = "purchase_quantity" min="1" max="6" delta="0" style = "width: 32px;" value="1">
+
</div>
you should change $(".purchase_quantity").val(num) to $("#purchase_quantity").val(num)

JavaScript form same values

How can I make a form so they cannot repeat the same values in the Input?
I tried a way like:
var text1 = document.getElementById('num1').value;
var text2 = document.getElementById('num1').value;
var textform = [text1,text2];
if (
text1 == text2 ||
text2 == text1
) {
alert("repeated numbers");
return false;
}
But this is gets me into two troubles:
- If I put no value, it will say: Repated Numbers
- If I want to make this for 100 form values, it takes a lot of code
You could give all of your text elements the same class, and grab their values by class name to simplify building the array of text values.
<input type="text" class="checkDupe" id="input1" />
<input type="text" class="checkDupe" id="input2" />
Then grab their values in javascript
var checkDupes = document.getElementsByClassName('checkDupe');
var textArray = [];
for(var i = 0; i < checkDupes.length; i++){
textArray.push(checkDupes[i].value);
}
Now that we have an array of values that they entered, check to see if any of them repeat by sorting the array, and seeing if any two elements side-by-side are the same.
textArray.sort();
var dupes = false;
for(var i = 0; i < textArray.length; i++){
if(textArray[i] === textArray[i + 1]) dupes = true;
}
If we find any duplicates, let the user know.
if(dupes) alert('Repeated numbers!');
You could do something like this:
var text1 = document.getElementById('num1').value;
var text2 = document.getElementById('num2').value;
var textform = [text1, text2];
var seen = {};
textform.forEach(function(value) {
if (seen[value]) {
alert('Bad!');
}
seen[value] = true;
});
In the code above, we loop over each value in the array. The first time we encounter it, we push it into a map. Next time (if) we hit that value, it will exist in the map and it will tell us we've seen it before.
If you give all the input's a common class then you quickly loop through them.
The HTML:
<input type="text" name="num1" class="this that number"></input>
<input type="text" name="num2" class="this number"></input>
<input type="text" name="num3" class="that number"></input>
<input type="text" name="num4" class="number"></input>
<input type="text" name="num5" class=""></input> <!-- we don't want to check this one -->
<input type="text" name="num6" class="number that this"></input>
<input type="text" name="num7" class="this that number"></input>
The JavaScript:
// get all the inputs that have the class numbers
var ins = document.querySelectorAll("input.numbers");
// a tracker to track
var tracker = {};
// loop through all the inputs
for(var i = 0, numIns = ins.length; i < numIns; ++i)
{
// get the value of the input
var inValue = ins[i].value.trim();
// skip if there is no value
if(!inValue) continue;
// if the value is already tracked then let the user know they are a bad person
// and stop
if(tracker[inValue])
{
alert("You are a bad person!");
return;
}
// track the value
tracker[inValue] = true;
}
You could also enhance this to let the user know which inputs have duplicate values:
// get all the inputs that have the class numbers
var ins = document.querySelectorAll("input.numbers");
// a tracker to track
var tracker = {};
// loop through all the inputs
for(var i = 0, numIns = ins.length; i < numIns; ++i)
{
// get the value of the input
var inValue = ins[i].value.trim();
// skip if there is no value
if(!inValue) continue;
// if the value is already tracked then error them
if(tracker[inValue])
{
// mark the current input as error
ins[i].className += " error";
// mark the first found instance as an error
ins[tracker[inValue]].className += " error";
}
// save the index so we can get to it later if a duplicate is found
tracker[inValue] = i;
}
Here's a way of doing it that automatically picks up all the text inputs in your document and validates based on what you're looking for. Would be simple enough to expose the valid value and make this the validation handler (or part of one) that handles a form submission.
<meta charset="UTF-8">
<input id="num1" type="text" value="foobar1">
<input id="num2" type="text" value="foobar2">
<input id="num3" type="text" value="foobar3">
<input id="num4" type="text" value="foobar4">
<input id="num5" type="text" value="foobar5">
<button onClick="checkValues();">Validate</button>
<script>
function checkValues() {
var inputs = document.getElementsByTagName('input');
arrInputs = Array.prototype.slice.call(inputs);
var valid = true;
var valueStore = {};
arrInputs.forEach(function(input) {
if (input.type == 'text') {
var value = input.value.toUpperCase();
if (valueStore[value]) {
valid = false;
} else {
valueStore[value] = true;
}
}
});
if (valid) {
alert('Valid: No matching values');
} else {
alert('Invalid: Matching values found!');
}
}
</script>
With jquery you can iterate directly over the inputs.
<form>
<input type="text" >
<input type="text" >
<input type="text" >
<input type="text" >
<input type="text" >
<input type="text" >
<button>
TEST
</button>
</form>
function checkValues(){
var used = {};
var ok = true;
$('form input[type="text"]').each(function(){
var value = $(this).val();
if(value !== ""){
if(used[value] === true){
ok = false;
return false;
}
used[value] = true;
}
});
return ok;
}
$('button').click(function(event){
event.preventDefault();
if(!checkValues()){
alert("repeated numbers");
};
});
https://jsfiddle.net/8mafLu1c/1/
Presumably the inputs are in a form. You can access all form controls via the form's elements collection. The following will check the value of all controls, not just inputs, but can easily be restricted to certain types.
If you want to include radio buttons and checkboxes, check that they're checked before testing their value.
function noDupeValues(form) {
var values = Object.create(null);
return [].every.call(form.elements, function(control){
if (control.value in values && control.value != '') return false;
else return values[control.value] = true;
});
}
<form id="f0" onsubmit="return noDupeValues(this);">
<input name="inp0">
<input name="inp0">
<input name="inp0">
<button>Submit</button>
</form>
For old browsers like IE 8 you'll need a polyfill for every.
You can simply get all inputs iterate them twice to check if they are equals
var inputs = document.getElementsByTagName('input');
for (i = 0; i < inputs.length; i++) {
for (j = i + 1; j < inputs.length; j++) {
if (inputs[i].value === inputs[j].value) {
console.log('value of input: ' + i + ' equals input: ' + j);
}
}
}
<input value="56" />
<input value="12" />
<input value="54" />
<input value="55" />
<input value="12" />

Add another condition to Show/Hide Divs

I have the follow script on a form.
jQuery(document).ready(function($) {
$('#bizloctype').on('change',function() {
$('#packages div').show().not(".package-" + this.value).hide();
});
});
</script>
Basically, depending on the value of the select box #bizloctype (value="1","2","3" or "4") the corresponding div shows and the rest are hidden (div class="package-1","package-2","package-3", or "package-4"). Works perfectly.
BUT, I need to add an additional condition. I need the text box #annualsales to be another condition determining which div shows (if the value is less than 35000 then it should show package-1 only, and no other packages.
I think the below script works fine when independent of the other script but I need to find out how to marry them.
<script>
$("#annualsales").change(function(){
$(".package-1,.package-2,.package-3,.package-4").hide();
var myValue = $(this).val();
if(myValue <= 35000){
$(".package-1").show();
}
else
{
$(".package-2").show();
}
});
</script>
Help please?
I would remove the logic from the anonymous functions and do something like this:
// handle display
function displayPackage( fieldID ) {
var packageType = getPackageType(fieldID);
$('#packages div').show().not(".package-" + packageType).hide();
}
// getting the correct value (1,2,3 or 4)
function getPackageType( fieldID ) {
// default displayed type
var v = 1;
switch(fieldID) {
case 'bizloctype':
// first check it annualsales is 1
v = (getPackageType('annualsales') == 1) ?
1 : $('#' + fieldID).val();
break;
case 'annualsales':
v = (parseInt($('#' + fieldID).val(),10) <= 35000) ? 1 : 2;
break;
}
return v;
}
jQuery(document).ready(function($) {
$('#bizloctype,#annualsales').on('change',function() {
displayPackage($(this).attr('id'));
});
});
If I understand your question properly, try this code out. It first adds an onChange listener to #annualsales which is the code you originally had. Then, for the onChange listener for #bizloctype, it simply checks the value of #annualsales before displaying the other packages.
jQuery(document).ready(function($) {
// Check value of #annualsales on change
$("#annualsales").change(function(){
$(".package-1,.package-2,.package-3,.package-4").hide();
var myValue = $(this).val();
if(myValue <= 35000){
$(".package-1").show();
}
// Only show other packages if value is > 35000
$('#bizloctype').on('change',function() {
$(".package-1,.package-2,.package-3,.package-4").hide();
if ($('#annualsales').val() <= 35000) {
$(".package-1").show();
} else {
$('#packages div').show().not(".package-" + this.value).hide();
}
});
});
Since you already use JQuery you can use the data() function to create a simple but dynamic condition system. For example, you could annotate each element with the required conditions and then attach change listeners to other elements to make the condition active or inactive for the elements.
For example, with this HTML:
<div id="conditions">
Condition 1: <input type="checkbox" id="check1" /> <= check this<br/>
Condition 2: <input type="checkbox" id="check2" /><br/>
Condition 3: <input type="text" id="value1" /> <= introduce 1001 or greater<br/>
Condition 4: <input type="text" id="value2" /><br/>
</div>
<p id="thing" data-show-conditions="check1 value1-gt-1000"
style="display: none;">
The thing to show.
</p>
And this Javascript:
function setShowCondition(el, condition, active) {
var conditions = $(el).data('conditions') || {};
conditions[condition] = active;
$(el).data('conditions', conditions);
var required = ($(el).data('show-conditions') || "").split(" ");
var visible = required.every(function (c) {
return conditions[c];
});
if (visible) {
$(el).show();
} else {
$(el).hide();
}
}
$("#conditions input[type='checkbox'").change(function () {
setShowCondition('#thing',
$(this).attr('id'),
$(this).is(':checked'));
});
$("#value1").change(function () {
var number = parseInt($(this).val());
setShowCondition('#thing', 'value1-gt-1000', number > 1000);
});
You can maintain conditions easily without having to nest and combine several if statements.
I've prepared a sample to show this in https://jsfiddle.net/2L5brd80/.

Pasting multiple numbers over multiple input fields

I've got a form on my site using 6 input fields. The site visitor simply enters a 6 digit code into these 6 boxes. The thing is that they'll get the 6 digit code and it would be ideal to allow them to simply copy the 6 digit code we send them into these input fields by simply putting pasting into the first input field and having the remaining 5 digits go into the remaining 5 input fields. It would just make it much easier than having to manually enter each digit into each input field.
Here's the code we're currently using, but it can easily be changed to accomplish what is described above:
<input type="text" maxlength="1" class="def-txt-input" name="chars[1]">
<input type="text" maxlength="1" class="def-txt-input" name="chars[2]">
<input type="text" maxlength="1" class="def-txt-input" name="chars[3]">
<input type="text" maxlength="1" class="def-txt-input" name="chars[4]">
<input type="text" maxlength="1" class="def-txt-input" name="chars[5]">
<input type="text" maxlength="1" class="def-txt-input" name="chars[6]">
I saw a posting similar to this here: Pasting of serialnumber over multiple textfields
But it doesn't have the solution I'm looking for. Ideally this could be pulled off using jQuery or plain JavaScript.
Edit
I didn't like the timer solution I used in the paste event and the complexity of just using the input or paste event.
After looking at this for a while I added a solution which uses a hybrid between the 2.
The code seems to do all that is required now.
The Script:
var $inputs = $(".def-txt-input");
var intRegex = /^\d+$/;
// Prevents user from manually entering non-digits.
$inputs.on("input.fromManual", function(){
if(!intRegex.test($(this).val())){
$(this).val("");
}
});
// Prevents pasting non-digits and if value is 6 characters long will parse each character into an individual box.
$inputs.on("paste", function() {
var $this = $(this);
var originalValue = $this.val();
$this.val("");
$this.one("input.fromPaste", function(){
$currentInputBox = $(this);
var pastedValue = $currentInputBox.val();
if (pastedValue.length == 6 && intRegex.test(pastedValue)) {
pasteValues(pastedValue);
}
else {
$this.val(originalValue);
}
$inputs.attr("maxlength", 1);
});
$inputs.attr("maxlength", 6);
});
// Parses the individual digits into the individual boxes.
function pasteValues(element) {
var values = element.split("");
$(values).each(function(index) {
var $inputBox = $('.def-txt-input[name="chars[' + (index + 1) + ']"]');
$inputBox.val(values[index])
});
};​
See DEMO
Here is an example of a jquery plugin that does the same thing as the original answer only generalized.
I went to great lengths to modify the original answer ( http://jsfiddle.net/D7jVR/ ) to a jquery plugin and the source code is here: https://github.com/relipse/jquery-pastehopacross/blob/master/jquery.pastehopacross.js
An example of this on jsfiddle is here:
http://jsfiddle.net/D7jVR/111/
The source as of 4-Apr-2013 is below:
/**
* PasteHopAcross jquery plugin
* Paste across multiple inputs plugin,
* inspired by http://jsfiddle.net/D7jVR/
*/
(function ($) {
jQuery.fn.pastehopacross = function(opts){
if (!opts){ opts = {} }
if (!opts.regexRemove){
opts.regexRemove = false;
}
if (!opts.inputs){
opts.inputs = [];
}
if (opts.inputs.length == 0){
//return
return $(this);
}
if (!opts.first_maxlength){
opts.first_maxlength = $(this).attr('maxlength');
if (!opts.first_maxlength){
return $(this);
}
}
$(this).on('paste', function(){
//remove maxlength attribute
$(this).removeAttr('maxlength');
$(this).one("input.fromPaste", function(){
var $firstBox = $(this);
var pastedValue = $(this).val();
if (opts.regexRemove){
pastedValue = pastedValue.replace(opts.regexRemove, "");
}
var str_pv = pastedValue;
$(opts.inputs).each(function(){
var pv = str_pv.split('');
var maxlength;
if ($firstBox.get(0) == this){
maxlength = opts.first_maxlength;
}else{
maxlength = $(this).attr('maxlength');
}
if (maxlength == undefined){
//paste them all!
maxlength = pv.length;
}
//clear the value
$(this).val('');
var nwval = '';
for (var i = 0; i < maxlength; ++i){
if (typeof(pv[i]) != 'undefined'){
nwval += pv[i];
}
}
$(this).val(nwval);
//remove everything from earlier
str_pv = str_pv.substring(maxlength);
});
//restore maxlength attribute
$(this).attr('maxlength', opts.first_maxlength);
});
});
return $(this);
}
})(jQuery);
This shouldn't be too difficult ... add a handler for the paste event on the first input, and then process per the requirement.
Edit
Actually this is much trickier than I thought, because it seems there's no way to get what text was pasted. You might have to kind of hack this functionality in, using something like this (semi-working)... (see the JSFiddle).
$(document).on("input", "input[name^=chars]", function(e) {
// get the text entered
var text = $(this).val();
// if 6 characters were entered, place one in each of the input textboxes
if (text.length == 6) {
for (i=1 ; i<=text.length ; i++) {
$("input[name^=chars]").eq(i-1).val(text[i-1]);
}
}
// otherwise, make sure a maximum of 1 character can be entered
else if (text.length > 1) {
$(this).val(text[0]);
}
});
HTML
<input id="input-1" maxlength="1" type="number" />
<input id="input-2" maxlength="1" type="number" />
<input id="input-3" maxlength="1" type="number" />
<input id="input-4" maxlength="1" type="number" />
jQuery
$("input").bind("paste", function(e){
var pastedData = e.originalEvent.clipboardData.getData('text');
var num_array = [];
num_array = pastedData.toString(10).replace(/\D/g, '0').split('').map(Number); // creates array of numbers
for(var a = 0; a < 4; a++) { // Since I have 4 input boxes to fill in
var pos = a+1;
event.preventDefault();
$('#input-'+pos).val(num_array[a]);
}
});
You're going to have to right some custom code. You may have to remove the maxlength property and use javascript to enforce the limit of one number per input.
As dbasemane suggests, you can listen for a paste event. You can listen to keyup events too to allow the user to type out numbers without having to switch to the next input.
Here is one possible solution:
function handleCharacter(event) {
var $input = $(this),
index = getIndex($input),
digit = $input.val().slice(0,1),
rest = $input.val().slice(1),
$next;
if (rest.length > 0) {
$input.val(digit); // trim input value to just one character
$next = $('.def-txt-input[name="chars['+ (index + 1) +']"]');
if ($next.length > 0) {
$next.val(rest); // push the rest of the value into the next input
$next.focus();
handleCharacter.call($next, event); // run the same code on the next input
}
}
}
function handleBackspace(event) {
var $input = $(this),
index = getIndex($input),
$prev;
// if the user pressed backspace and the input is empty
if (event.which === 8 && !$(this).val()) {
$prev = $('.def-txt-input[name="chars['+ (index - 1) +']"]');
$prev.focus();
}
}
function getIndex($input) {
return parseInt($input.attr('name').split(/[\[\]]/)[1], 10);
}
$('.def-txt-input')
.on('keyup paste', handleCharacter)
.on('keydown', handleBackspace);
I have this code set up on jsfiddle, so you can take a look at how it runs: http://jsfiddle.net/hallettj/Kcyna/

Categories

Resources