How can I write this repetitive JS function more cleanly? - javascript

I have a function that will populate a table #contents with some rows. It looks like this:
// data is defined in data.js
function populateTable() {
for(var i = 0; i < data.length; i++) {
if (data[i].type == "MOV") {
var row = '<tr><td><a>' + data[i].title + '</a></td>';
row+= '<td><a>' + data[i].year+ '</a></td></tr>';
$("#contents").append(row);
} else {
var row = '<tr><td><a>' + data[i].title + '</a></td>';
row+= '<td>' + data[i].year+ '</td></tr>';
$("#contents").append(row);
}
}
}
I'm going to potentially have a bunch of content types, so this IF statement will get a good deal bigger, I'm wondering what a better way to handle the repeated elements inside the IF statement might be?

You do not need to repeat the string that do not vary for each if statement, That would shorten up your code a lot
function populateTable() {
function getUniqueString({type, year}) {
if(type == 'MOV')
return `<a>${year}</a>`
return `${year}`
}
for (const d of data) {
$("#contents").append(`<tr>
<td><a>${d.title}</a></td>
<td>${getUniqueString(d)}</td>
</tr>`)
}
}

Your using var so I am assuming your a beginner. So watch a youtube video or research : template strings and higher order functions.
Template strings or much cleaner way of writing strings because you don't have to use the + sign
Higher order functions are better and cleaner way of looping over arrays.
Oh it also does not hurt to use some line spacing. It really improves readability

Related

how can I append some html code using js several times

Hy guys, I have the following problem...
I need to draw in some cases (ex. when edit, save, update, etc) a defined html code (is the one at the end of the post) using js but I don't want to explicitly add the whole code every time i need it, so I'm wondering... ¿Is there a better way than adding it to a var and then .append or .html the code? instead of building the code every time I want to make it into an object oriented approach with properties and building it when needed, is there a way?
This is the code
for (var i = 1; i <= lineas; i++) {
c += "<div class='row'><section>some html for example</section></div>";
c += "<div>some other stuff</div>";
}
$('#res').append(c);
By a better way to do this... I mean... or I'm looking for, some kind of object oriented way to doit, hope you guys get it.
Regards!
If you mostly use the same formats, you can write an object that will define some commonly-used (in your judgement) helpers:
var helper = {
sectionInDiv: function(divClass, sectionText) {
var result = "<div class='" + divClass +"'><section>" + sectionText +"</section></div>";
return result;
},
divOnly: function(text) {
var result = "<div>" + text + "</div>";
return result;
},
repeat: function (n, htmlText){
var result = "";
for (var i = 1; i <= n; i++) {
result += htmlText;
}
return result;
}
...
}
and then use it:
var htmlToRepeat =
helper.sectionInDiv("row", "some example text") +
helper.divOnly("some more text");
helper.repeat(10, htmlToRepeat);

Populate a prompt with elements of an array and number them off

(Stack Overflow doesn't have a tag for 'prompt' so I have used alert as I am guessing it is similar enough to attract the right answerers.)
Hello,
I am currently making a JavaScript-based game for an assignment at university. I am usually pretty good with problem solving but have been stumped by this issue.
To explain, I have an array which names the possible armour slots the player can pick. In any order these can be picked, and each time the choice gets pushed to a second array which handles what has already been picked (and in what order) and that item gets spliced from the original array. There is a while loop which runs through until all 3 have been picked.
var armourSlotToPick = ["Head", "Chest", "Legs"],
armourSlotPicked = [],
armourLoop = 1,
indexArmour = 0;
function numInArray() {
indexArmour++;
return (indexArmour + ". " + armourSlotToPick[indexArmour - 1] + "\n");
}
function armour() {
while (armourLoop < 4) {
var armourPick = prompt("Pick an armour slot to generate an item for:\n" + armourSlotToPick.forEach(numInArray));
if (armourPick == 1) {
armourSlotPicked.push(armourSlotToPick[0]);
armourSlotToPick.splice(0,1);
} else if (armourPick == 2) {
armourSlotPicked.push(armourSlotToPick[1]);
armourSlotToPick.splice(1,1);
} else if (armourPick == 3) {
armourSlotPicked.push(armourSlotToPick[2]);
armourSlotToPick.splice(2,1);
} else {
alert("Invalid choice, you suck");
break;
}
armourLoop++;
}
}
I know it probably wouldn't be possible to do the whole return in numInArray() to the prompt, but it shows some working.
Now the problem: I got it working so that each item in the array was numbered (var armourSlotToPick = ["1. Head", "2. Chest", "3. Legs"],) but as you could see, if the player chose 2, then the next time it would show "1. Head (new line) 3. Legs" and when the player chooses 3, a problem would occur, as they were really meant to choose 2. How is it possible to number the items in the array, in a prompt?
I'm possibly over thinking this but I have suffered for a few hours now.
I thank you in advance for any insight you may have,
Daniel.
EDIT: Solved.
Below is the end result, a slight variation from the edited answer from Jonathan Brooks.
var armourSlotToPick = [null, "Head", "Chest", "Legs"]
var armourSlotPicked = [null];
var armourLoop = 1;
function armour() {
while (armourLoop < 4) {
var message = "Pick an armour slot to generate an item for:\n";
for (var i = 0; i < armourSlotToPick.length; i++) {
if (armourSlotToPick[i] !== null) {
message += "" + i + ". " + armourSlotToPick[i] + "\n";
}
}
var armourPick = prompt(message);
if (armourPick > armourSlotToPick.length-1 || armourPick < 1) {
alert("Invalid choice, you suck");
} else {
var insert = armourSlotToPick.splice(armourPick, 1);
armourSlotPicked.push(insert);
}
armourLoop++;
}
armourSlotPicked.splice(0,1);
}
armour();
alert(armourSlotPicked.join("\n"));
I thank all that have contributed to this discussion and the end result, and I hope this is a good example for future problems people may have similar to this.
Check out my fiddle, I think I have a working solution.
What you really want to be using are Object Literals with your own indexing (starting from 1) - if it were me, I would create my own way to iterate over this custom indexing by adding a method to the Object's prototype, but I digress.
You're overcomplicating your code by using a while loop, and that large bulk of if statements is unnecessary: instead, all you need is some basic validation on the input and then you can just trust whatever input passes this validation. That is demonstrated here:
if ( armourPick > armourSlotToPick.length || armourPick < 1 ) {
alert("Invalid choice, you suck");
}
else {
armourSlotPicked.push( armourSlotToPick[armourPick-1] )
alert (armourSlotPicked[armourSlotPicked.length-1].value);
}
Read my code carefully, and you should get a better understanding of how to deal with certain issues.
EDIT:
As per your request, I think I have a solution that suits your needs. Basically all you have to do to have the arrays "start" at an index of 1 is to fill the zeroth element with a null value, like so:
var armourSlotToPick = [null, "Head", "Chest", "Legs"]
var armourSlotPicked = [null];
You just have to remember to take this null object into account in your code, for example:
if (armourSlotToPick[i] !== null) {
message += "" + i + "\n";
}
The indices will update automatically. See this updated fiddle for more details.
use structures / objects as content in the array, instead of just values.
the basic concept:
armourSlotPicked.push({ "key": 1, "value":armourSlotToPick[1]})
alert("value: " + armourSlotPicked[0].value)
alert("key: " + armourSlotPicked[0].key)
edit: responding to comments can take some space.
IMHO a prompt is the completely wrong tool for this, since most browsers would ask the user permission to prevent multiple popups, and since a promt can only return 1 piece of information, you can only ask for 1 thing per popup. Instead you ought to use a div element, with checkboxes for each information..
That being said it can easily be used in a promt.
The prompt is just a built in function, that takes a string as an argument (which is shown as text in the popup) and returns a string with the users input.
what does the magic for you is in fact this:
array.foreach(): The forEach() method executes a provided function once per array element.
in your case that means it calls a function that returns a string for each element in the array, and concatenates the strings.
in the old days you would have written this:
var messageText= "Pick an armour slot to generate an item for:\n"
for(var i = 1; i < armourSlotToPick.length; i++){
messageText += i + ". " + armourSlotToPick[i- 1] + "\n";
}
var armourPick = prompt(messageText);
but in this modern age, you define a printing function, and use it to generate the loop:
function numInArray() {
indexArmour++;
return (indexArmour + ". " + armourSlotToPick[indexArmour - 1] + "\n");
}
//more code before we get to where the function is used....
indexArmour = 0;
var messageText = "Pick an armour slot to generate an item for:\n" + armourSlotToPick.forEach(numInArray);
var armourPick = prompt(messageText);
or in a single line as in your code:
indexArmour = 0; //you forgot this - otherwise the list will only be complete once?
var armourPick = prompt("Pick an armour slot to generate an item for:\n" + armourSlotToPick.forEach(numInArray));
It produces the same output, because it does the same thing, its just written very differently!
If the array holds "object literals" instead of simply values, as I suggest, the old fashioned code would look something like this:
function contains(a, value) {
try{
for (var i = 0; i < a.length; i++) {
if (a[i].value == value) {
return true;
}
}
}
catch(err) {
// do nothing
};
return false;
}
and later..
for(var j = 0; j < 4; j++){
for(var i = 0; i < Math.min(armourSlotToPick.length); i++){
if( contains(armourSlotPicked, armourSlotToPick[i- 1]) )
continue;
var messageText = "Generate an item for armour in slot: " + i + "\n"
messageText += armourSlotToPick[i- 1] + "\n";
}
var armourPick = prompt(messageText);
if (armourPick > 0 && armourPick < armourSlotToPick.length) {
armourSlotPicked.push({"key":j, "value":armourSlotToPick[armourPick]);
}
...
}
//now we have an array that holds information about when what was picked..
or something along those lines.. this is bt.w completely untested, it's just for illustration
You want to use the array index to number your items. Since your numbers are one-based and the index is zero-based, you will need to convert between the two when outputting and interpreting the response.
This approach will also allow you to eliminate all but two of the cases in your if-else statement.

Update a global variable in a Javascript function without using the variable name

I am working on a replica of an old game I played in High School called Drug Wars. I have global variables that hold the qty of each drug owned. Originally I just used a different function for each buy and sell operation for each drug. Trying to save myself some code and provide for some scale ability I wanted to change it to one function for buying, passing the drugqty, drugprice(done by a different function), and the drugname.
The part I am stuck with is trying to update the drugqty (used locally in the function) to the global variable.
function buy(drugqty, drugprice, drugname)
{
if (money < drugprice)
{
window.alert("You do not have enough money to do that!")
}
else if(money >= drugprice)
{
var maxdrug = money/drugprice;
var addtoqty;
addtoqty=prompt("How many would you like to purchase? They are $" + drugprice +" a unit. You can afford " + Math.floor(maxdrug) + "." );
if((addtoqty*drugprice)>money)
{
window.alert("Nice try you need more money to do that!")
}
else {
drugqty = parseInt(drugqty, 10) + parseInt(addtoqty, 10);
money = money - (drugprice*addtoqty);
document.getElementById(drugname+"qty").innerHTML=drugqty;
document.getElementById("money").innerHTML=money;
}
}
}
Below is my buy button for one particular drug.
<td><center><button onclick="buy(cocaineqty, cocaine, 'cocaine')">Buy</center></button></center></td>
When the button is pressed it calls the function and executes correctly.
I just need to figure out how to update the global variable cocaineqty with what the local function drugqty creates.
I tried things like
drugname+"qty"=drugqty;
My two thoughts were to parse that information again from where it is displayed or somehow update the global variable in the function(again using it for more than one drug in the future so it can be done dynamically)
<td id="cocaineqty" align="center">0</td>
My first post here but I am more than willing to make changes or correct any mistakes with the post. Thanks for your time!
edit: Updated my code for the correct information based on the checked answer
function buy(drugqty, drugprice, drugname)
{
if (money < drugprice)
{
window.alert("You do not have enough money to do that!")
}
else if(money >= drugprice)
{
var maxdrug = money/drugprice;
var addtoqty;
addtoqty=prompt("How many would you like to purchase? They are $" + drugprice +" a unit. You can afford " + Math.floor(maxdrug) + "." );
if((addtoqty*drugprice)>money)
{
window.alert("Nice try you need more money to do that!")
}
else {
drugqty = parseInt(drugqty, 10) + parseInt(addtoqty, 10);
money = money - (drugprice*addtoqty);
document.getElementById(drugname+"qty").innerHTML=drugqty;
document.getElementById("money").innerHTML=money;
window[drugname+"qty"]=drugqty;
}
}
Thanks again for all of your assistance!
Firstly they way this im implemented is not awesome. There is a lot that could be done better but Im not going to try to change you implementation here... :)
To do what you want to do, that is access a global variable without knowing its actual name, can be done like this.
window[drugname + "qty"] = drugqty;
This works because
"Global" variable in a browser are actually on the window object
objects in JavaScript are associative arrays. Which in simple terms means you can access its properties via it name as you would in a Key Value pair
You can use the eval() function for this:
eval(drugname+"qty"=drugqty);
Note: It would be much better to learn about Object Oriented programming. So you can make a Drug class and class instances (objects) of this class to store the particular drug information. It is easy pass around these classes and keep track of the information!
You could save all your quantities in a single object and add a property per drug.
var quantities = {};
quantities[drugname + "qty"] = drugqty;
But, if you want to display the results also, it might be easier to leave out the "qty"-part:
var quantities = {};
quantities[drugname] = drugqty;
And for displaying the results:
var html = "<table>";
for (var quantityName in quantities) {
if (quantities.hasOwnProperty(quantityName)) {
html += "<tr><td>" + quantityName + "</td>";
html += "<td id='" + quantityName + "qty' align='center'>" + quantities[quantityName] + "</td></tr>";
}
}
html += "</table>";

Show/Hide image before filtering a table isn't working

I have web page with some really large tables that I'm filtering using some jquery routines I wrote. Anyway, when these tables get really large and the filtering functions can take some time to complete. So I figured I'd unhide a animated gif so the user had some feedback. However, the gif never appears when I call:
$('#loadingimg').show();
Unless I put an alert statement in front of it. I apologize for the ugly code, I'm not an experienced jquery/javascript programmer.
function filter()
{
var eles = ["mtmprogram","rate","stage"];
var tag;
var classes='';
$('#loadingimg').show();
//alert('hi');
$('.report').hide();
for (var i in eles)
{
tag = '#' + eles[i] + ' option:selected';
if ($(tag).val())
{
//$('.'+ $(tag).val()).show();
classes = classes + '.' + $(tag).val();
}
}
if (classes == '')
$('tr.report').show();
else
$(classes).show();
filterSubtables('Loan Number');
$('#loadingimg').hide();
}
Many thanks!
Maybe you aren't giving the #loadingimg element enough time to display. You could test this by running the rest of your code in a timeout:
function filter()
{
var eles = ["mtmprogram","rate","stage"],
classes = '';
$('#loadingimg').show();
//alert('hi');
setTimeout(function () {
$('.report').hide();
for (var i = 0, len = eles.length; i < len; i++)
{
var $tag = $('#' + eles[i] + ' option:selected');
if ($tag.val())
{
//$('.'+ $tag.val()).show();
classes = classes + '.' + $tag.val();
}
}
if (classes == '')
$('.report').show();
else
$(classes).show();
filterSubtables('Loan Number');
$('#loadingimg').hide();
}, 500);
}
Notice that I changed how the tag variable is used (this creates less CPU overhead to make less jQuery selections and to use as local a variable as possible). I also changed your loop to a better format that performs amazingly faster than for ( a in b ): http://jsperf.com/jquery-each-vs-for-loops/2

How to optimize jquery grep method on 30k records array

Is it possible to optimize this code? I have very low performance on this keyup event.
$('#opis').keyup(function () {
if ($('#opis').val() != "") {
var search = $.grep(
svgs, function (value) {
reg = new RegExp('^' + $('#opis').val(), 'i');
return value.match(reg) == null;
}, true);
$('#file_list').html("");
var tohtml = "";
$cnt = 0;
for (var i = 0; i < search.length; i++) {
if ($cnt <= 30) {
tohtml += "<li class='file_item'><a href='' class='preview'>" + search[i] + "</a> <a href='" + search[i] + "' class='print_file'><img src='img/add16px.png' alt='dodaj'/></li></a>";
$cnt++;
} else {
break;
}
}
$('#file_list').html(tohtml);
$(".preview").click(function () {
$('#file_preview').html('<embed src="opisy/' + $(this).html() + '" type="image/svg+xml" pluginspage="http://www.adobe.com/svg/viewer/install/" /> ');
$(".preview").parent().removeClass("selected");
$(this).parent().addClass("selected");
return false;
});
$(".print_file").click(function () {
if (jQuery.inArray($(this).attr('href'), prints) == -1) {
$('#print_list').append('<li>' + $(this).attr('href') + '</li>');
prints.push($(this).attr('href'));
} else {
alert("Plik znajduje się już na liście do wydruku!");
}
return false;
});
} else {
$('#file_list').html(" ");
}
});
var opis = $('#opis')[0]; // this line can go outside of keyup
var search = [];
var re = new RegExp('^' + opis.value, 'i');
for (var i = 0, len = svgs.length; i < len; i++) {
if (re.test(svgs[i])) {
search.push(svgs[i]);
}
}
It's up to 100x faster in Google Chrome, 60x in IE 6.
first thing you have to learn:
$('#opis').keyup(function() {
$this = $(this);
if($this.val()!=""){
// so *$this* instead of *$('#opis')*
// because you are reperforming a *getElementById("opis")* and you've already called it when you used the keyup method.
// and use $this instead of $(this) | pretty much the same problem
so about the grep function, maybe if you cache the results it would help in further searchs I guess, but I don't know if can help you with that
Well the thing with javascript is that it executes under the users environment and not the servers environment so optimization always varies, with large large arrays that need extensive work done on them to I would prefer to handle this server side.
Have you thought about serializing the data and passing them over to your server side, which would handle all the data calculations / modifications and return the prepared result back as the response.
You may also want to take alook at SE:Code Review for more optimization advise.
Some optimization, tips:
if($('#opis').val()!=""){ should be using '!=='.
return value.match(reg)==null; should be ===.
for(var i=0;i<search.length;i++){
reg = new RegExp(...); should be var reg ... as its not defined outside the function as a global.
Move all your variable declarations to the top of the function such as
var i,cnt,search,tohtml etc
i would advise you to start using Google Chrome, it has a built in system for memeory tracking on perticular tabs, you can go to the url about:memory in chrome, which would produce a result like so:
Image taken from: http://malektips.com/google-chrome-memory-usage.html
Each time you perform the grep, you are calling the 'matching' function once per array entry.
The matching function creates a RegExp object and then uses it to perform the match.
There are two ways you could improve this:
Create the RegExp once, outside of the function, and then use a closure to capture it inside the function, so that you don't have to keep recreating the object over and over.
It looks like all you're trying to do is to perform a case-insensitive tests to see whether the sought string is the start of a member of your array. It may be faster to do this more explicitly, using .toUpper and substring. However, that's a guess and you should test to find out.

Categories

Resources