How do I make this code DRY?

Just started using JavaScript and trying to code something myself. I have the following JS Fiddle: https://jsfiddle.net/4ztq1gLe/

HTML

<ul>
		<li>1:<input type="text" id="str1"> gives:<span id="strReturn1"></span></li>
		<li>2:<input type="text" id="str2"> gives:<span id="strReturn2"></span></li>
		<li>3:<input type="text" id="str3"> gives:<span id="strReturn3"></span></li>
		<li>4:<input type="text" id="str4"> gives:<span id="strReturn4"></span></li>
	</ul>

JavaScript

$("#str1").keypress(function(event){
var str1=this.value;
if(event.which === 13){
$("#strReturn1").text(str1);
}
});


$("#str2").keypress(function(event){
var str2=this.value;
if(event.which === 13){
$("#strReturn2").text(str2);
}
});

$("#str3").keypress(function(event){
var str3=this.value;
if(event.which === 13){
$("#strReturn3").text(str3);
}
});

$("#str4").keypress(function(event){
var str4=this.value;
if(event.which === 13){
$("#strReturn4").text(str4);
}
});

When you enter data and press enter it’s returned after the input field. But obviously the JS code is just the same code repeated 4 times. Been figuring out all day how to shorten this to a single function, but so my JS knowledge falls short.

Is there anyone who could help me out with this?

Use a for loop. I’m not an expert in Javascript, but with concatenation you might have something like this:

for (var i = 1; i <= 4; i++) {
  
// use i concatenated with the rest of the variable
// example: str + i for str1, str2, etc 
  
}

Thank you for your answer! I have modified the code to something like this:

for (var i = 1; i <= 4; i++) {
$("#str[i]").keypress(function(event){
var str[i]=this.value;
if(event.which === 13){
$("#strReturn[i]").text(str[i]);
}
});

I have a feeling that I’m close, but I’m having difficulty figuring out why this code doesn’t work.

I think it’s because you are not carrying the value of the button pressed to the JS script. It is cycling through, but it doesn’t know which button the cycled number matches up with.

for (var i = 1; i &lt;= 4; i++) {
    $("#str" + i).keypress(function(event){
         var str[i]=this.value;
         if(event.which === 13){
             $("#strReturn" + i).text(str[i]);
         }
    });
}

A new scope needs to be created for each iteration so that variable i isn’t the same value each time. This is done by wrapping the callback in a self invoking function and binding i to it. Also the string concatenation was incorrect as the previous commentor pointed out. What you were using looks like PHP and JavaScript does not behave the same as PHP.

for(var i = 0; i <= 4; i++) {
	$('#str' + i).keypress((function(index) {
		return function(evt) {
			if(evt.which === 13) {
				$('#strReturn' + index).text(this.value);
			}
		};
	})(i));
}
3 Likes

Or let / const if using ES6. String interpolation would be a possibility, too.

Its hard to tell these days who is using stuff and who is not unless they are obviously using a specific framework. People do still support older versions of IE.

Never worked with PHP, but apparently I’d be good at it :slight_smile:
Thank you for your help, with this code I’ll able to create most of the webpage I had in mind!

Actually, you might also do this with no (explicit) loop at all:

$('input[id^=str]').keypress(function (event) {
  if (event.keyCode === 13) {
    $(this).next().text(this.value)
  }
})

Of course, this only works because those span elements happen to be right next to the corresponding input elements. A more general solution would be to relate the elements via data-* attributes like so:

HTML

<ol>
  <li><input type="text" data-target="#strReturn1"></li>
  <li><input type="text" data-target="#strReturn2"></li>
  <li><input type="text" data-target="#strReturn3"></li>
  <li><input type="text" data-target="#strReturn4"></li>
</ol>

<div>Gives:</div>

<ol>
  <li><span id="strReturn1"></span></li>
  <li><span id="strReturn2"></span></li>
  <li><span id="strReturn3"></span></li>
  <li><span id="strReturn4"></span></li>
</ol>

JS

$('[data-target]').keypress(function (event) {
  if (event.keyCode === 13) {
    $(this.dataset.target).text(this.value)
  }
})

This code is easier to understand when you break the code out to a separate function.

However, if coding this from scratch, an improved solution is easily achieved without needing id attributes at all.

<ul>
    <li>1:<input type="text"> gives:<span></span></li>
    <li>2:<input type="text"> gives:<span></span></li>
    <li>3:<input type="text"> gives:<span></span></li>
    <li>4:<input type="text"> gives:<span></span></li>
</ul>

We can get all of the li elements and loop over them with:

var items = document.querySelectorAll("li");
Array.from(items).forEach(function (item) {
    ...
});

For each of the items, we want to get the input and span. The addEventListener() method is the proper and correct way to add event listeners, and we’ll be passing input and span to a wrapper function designed to gives us an appropriate handler method.

...
Array.from(items).forEach(function (item) {
    var input = item.querySelector("input");
    var span = item.querySelector("span");

    item.addEventListener("keypress", inputKeypressWrapper(input, span));
});

The inputKeypressWrapper() function will return a function. Thanks to a technique called closure, that returned function retains knowledge of the input and span.

function inputKeypressWrapper(input, span) {
    return function inputKeypressHandler(evt) {
        ...
    };
}

Lastly, it is within the inputKeypressHandler() function where we handle the keypresses.

The keyCode and which methods are now deprecated and have been removed from web standards, so we need to use better ones like code or key instead. These give string values for pressed keys.

If we use code we’ll get “Enter” or “NumpadEnter”, whereas with key we’ll just get “Enter”, so that’s the more suitable one to use in this case.

And lastly, we can just use innerHTML to replace the contents of the span element. We can use this in there, but the code is easier to understand when we actually see that it’s the input instead.

...
    return function inputKeypressHandler(evt) {
        if (evt.key === "Enter") {
            span.innerHTML = input.value;
        }
    };
...

Here’s the full code:

function inputKeypressWrapper(input, span) {
    return function inputKeypressHandler(evt) {
        if (evt.key === "Enter") {
            span.innerHTML = input.value;
        }
    };
}

var items = document.querySelectorAll("li");
Array.from(items).forEach(function (item) {
    var input = item.querySelector("input");
    var span = item.querySelector("span");

    item.addEventListener("keypress", inputKeypressWrapper(input, span));
});

It can easily be modified to suit different conditions too. For example, when you have other items on the page, placing a class name on the ul element allows you to easily restrict the code to only that area:

<ul class="showOnEnter">
var list = document.querySelector(".showOnEnter");
items = list.querySelectorAll("li");
...
1 Like

Thank you for all your valuable input. Just recently discovered this forum, you guys are awesome!

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.