jQuery drying up function - javascript

As you can see below, I clearly repeat myself over. I understand that this is bad practice.
So, how can the 4 duplicate lines of code within the if and else statement be refactored into one?
Some guidance toward better practice would be greatly appreciated. Also, any DRY references / tutorials that you found helpful in learning this technique.
$('.inner_wrap .details').click(function() {
var index = $('.inner_wrap .details').index(this);
$('.details').removeClass('here');
$(this).addClass('here');
$('.view').removeClass('active');
$(this).find('.view').addClass('active');
console.log(index);
if(index > 2){
index -= 3;
**// This and its corresponding else statement is the topic of the question**
$(this).closest('.outer_wrapper').prev('.outer_wrapper').find('.tabSlide').removeClass('tabShow');
$(this).closest('.outer_wrapper').prev('.outer_wrapper').find('.tabSlide:eq(' + index + ')').addClass('tabShow');
} else {
$(this).closest('.outer_wrapper').prev('.outer_wrapper').find('.tabSlide').removeClass('tabShow');
$(this).closest('.outer_wrapper').prev('.outer_wrapper').find('.tabSlide:eq(' + index + ')').addClass('tabShow');
}
return false;
});

Note that the code is repeated in both if and else, meaning, it's always executed. Just take it out of the statement:
if (index > 2) {
index -= 3;
}
var elt = $(this).closest('.outer_wrapper').prev('.outer_wrapper');
elt.find('.tabSlide').removeClass('tabShow');
elt.find('.tabSlide:eq(' + index + ')').addClass('tabShow');
Next, note that the jQuery object is just an array. You can simplify your code thus:
if (index > 2) {
index -= 3;
}
var elt = $(this).closest('.outer_wrapper').prev('.outer_wrapper').find('.tabSlide');
$(elt.removeClass('tabShow')[index]).addClass('tabShow');
Finally, we can eliminate the aux variable, used just to demonstrate how you call the same object:
if (index > 2) {
index -= 3;
}
$($(this).closest('.outer_wrapper').prev('.outer_wrapper').find('.tabSlide').removeClass('tabShow')[index]).addClass('tabShow');
Please, break this into more than one line of code :D
[EDIT]
OK, and just for fun, here's an even more extreme astronaut-type code, getting rid of the remaining if:
$($(this).closest('.outer_wrapper').prev('.outer_wrapper').find('.tabSlide').removeClass('tabShow')[(index <= 2 ? index : index - 3)]).addClass('tabShow');
BUT! It's awfully unreadable and IMO, you should stick with just the first step. Until it becomes a performance issue, don't overdo it. Applying the DRY rule at the cost of readability/maintainability or just sticking everything into a single line of code makes as much sense as reading & writing minified code. Ie, don't do it :).
[EDIT 2]
#StuartNelson reminded me of the existence of the $.eq() function, which would bring the final code to this (broken into several lines):
$(this).closest('.outer_wrapper')
.prev('.outer_wrapper')
.find('.tabSlide')
.removeClass('tabShow')
.eq(index <= 2 ? index : index - 3)
.addClass('tabShow');

You can modify the index only using the if statement and you could keep a variable for the jQuery element used:
$('.inner_wrap .details').click(function() {
var index = $('.inner_wrap .details').index(this);
$('.details').removeClass('here');
$(this).addClass('here');
$('.view').removeClass('active');
$(this).find('.view').addClass('active');
console.log(index);
if(index > 2){
index -= 3;
}
var element = $(this).closest('.outer_wrapper').prev('.outer_wrapper');
element.find('.tabSlide').removeClass('tabShow');
element.find('.tabSlide:eq(' + index + ')').addClass('tabShow');
return false;
});

if(index > 2){ index -= 3; }
**// This and its corresponding else statement is the topic of the question**
$(this).closest('.outer_wrapper').prev('.outer_wrapper')
.find('.tabSlide').removeClass('tabShow')
.eq(index).addClass('tabShow')
Ditch the else, since you're executing that code regardless you don't need to include it in the statement. You can just keep working on the chain, also, since you're first targeting all elements with class tabSlide and then targeting just a specific instance of that class based on its index.

Those two lines are executed last in both the if and else clause, so they can be pulled out of both and put after the entire if/else statement:
if(index > 2){
index -= 3;
}
$(this).closest('.outer_wrapper').prev('.outer_wrapper').find('.tabSlide').removeClass('tabShow');
$(this).closest('.outer_wrapper').prev('.outer_wrapper').find('.tabSlide:eq(' + index + ')').addClass('tabShow');
This rule is always true. If they were the first two lines in both your if and else clause you would pull them out and put them before your if statement instead.

Since all you really care about is setting the index, that is the only thing you need in the if staement, the rest can go out:
if(index > 2){
index -= 3;
}
$(this).closest('.outer_wrapper').prev('.outer_wrapper').find('.tabSlide').removeClass('tabShow');
$(this).closest('.outer_wrapper').prev('.outer_wrapper').find('.tabSlide:eq(' + index + ')').addClass('tabShow');

Related

How to make a JavaScript 'while' loop more elegant?

I've made a JavaScript 'while' loop to keep adding a random number from 1 to 10 to an array until the random number is 9 or 10.
function random() {
return Math.floor(Math.random() * (10)) + 1;
}
var array = [];
var element = 0;
while (element < 9) {
element = random();
if (element < 9) {
array.push(element);
}
}
console.log(array);
I have two questions.
How can I make the 'while' loop more elegant - without using (element < 9) two times?
How can I do this in a more elegant way, without using a 'while' loop?
To answer your first question, you can initialise element with a random value to begin with:
var element = random();
while (element < 9) {
array.push(element);
element = random();
}
This gets rid of the inner if.
To answer your second question, it doesn't get more elegant than this really. If you really want to use something else, you could use a do-while by changing around your code but really it's the same thing.
An even better alternative (I credit #NiettheDarkAbsol) for this, is to perform the assignment and check in one step:
while ((var element = random()) < 9) {
array.push(element);
}
You eliminate the need to even declare it outside, or call random() in more than one place.

Problems with the "+" and "-" operators

The problem is the - operator does not working (in 8th line). See my code below:
array = [0,0,0,0,3,0,0,0,0],
n = 0;
for(var i = 0; i < array.length; i++){
if(n < 9){ //the "n" variable there's only for don't crash the browser with a infinite loop
if(array[i] == 3){
array[i] = 0;
array[i - 1] = 3; //I believe that here is the problem
}
}
n++;
}
console.log(array);
So... I want to move the "3" value to the beginning of the array. But it only work if I use the + operator(in 8th line). Consequently, if I use the + one, the "3" value goes to the end of the array.
Anyone know why the - operator does not working in this case and the + works?
If you change line 8 to:
array[i+1] = 3;
then the number 3 will go all the way to the end of the array (well, beyond the end of the array and I'll be damned to find out what Javascript does then!). This is because the loop traverses the array in increasing order and the position i+1 will be checked right next.
On the other hand, with your current line 8, number 3 goes one position backwards (which has already been checked), so it doesn't go all the way to the beginning of the array, just one position. If you want it to go to all the way in the same fashion, you should reverse the loop (make it traverse the array in descending order of the position i).
What do you think happens when i is 0 and you do - 1?
In your first iteration of the loop, when i is zero, (i - 1) is -1, so you're trying to access array[-1], which is invalid.
Okay, instead of answering "Anyone know why the - operator does not working in this case and the + works?", I will answer what I think is the real question, as stated in the original post: I want to move the "3" value to the beginning of the array. I think this does what is desired:
var array = [0,0,0,0,3,0,0,0,0];
var first_val = 3;
var index = array.indexOf(first_val);
if (index > 0) {
array.splice(index, 1);
array.unshift(first_val);
}
console.log(array);
Inspired by this.
I want to move the "3" value to the beginning of the array
Use the correct answer provided in here. Then use .indexOf() to move it.
Array.prototype.move = function (old_index, new_index) {
if (new_index >= this.length) {
var k = new_index - this.length;
while ((k--) + 1) {
this.push(undefined);
}
}
this.splice(new_index, 0, this.splice(old_index, 1)[0]);
return this; // for testing purposes
};
var array = [0,0,0,0,3,0,0,0,0];
var result = array.move(array.indexOf(3),0);
console.log(result);
JSFiddle Demo

Nested If-else statements being skipped

What I'm building is a game where the computer generates a random number (1-100) and the user must guess the correct number. The goal is for the computer to compare the current guess to the previous guess and spit out a statement: "hot", "cold", "hotter", "colder", etc.
My Code (focus on the JS): CodePen fiddle
//global variables--computer generated guess, and guess log
var answer = Math.floor((Math.random() * 100)+1);
var guessArray = [];
var index = 0;
//user clicks submit button and guess is registered by computer
$("#submit").click( function(){
var guess = $("#guess").val();
guessArray.push(guess);
//prints out the answer and user guesses
$("#answer").text("Answer:" + " "+ answer);
$("#guessArrayPrint").text("You guessed: " + " " + guessArray + " ");
if (answer === guess) {
$("#statement").text("woo hoo right answer");
} else {
var currentDifference = Math.abs(answer-guess);
var currentDiffArray = [];
currentDiffArray.push(currentDifference);
if (index = 0) {
//if-else statement comparing current guess range to answer
if ( currentDifference >=1 && currentDifference <= 10){
$("#statement").text("Ouch! You're hot!");
} else {
$("#statement").text("Brr! You're cold!");
}
} else {
//if-else statement comparing current guess to previous guess
var previousDiff = answer- prevguess;
var prevguess = guessArray [i-1];
if( previousDiff < currentDifference){
$("#statement").text("Ahh! Getting Warmer!");
} else {
$("#statement").text("Brrr...getting colder");
}
}
index++
}
});
My nested if-else statements are not working. When a user inputs a guess, no matter how close to the answer, it always returns the statement "brr.. getting colder", which is in the "else" section.
Ideally when the user inputs their first guess if (index = 0) should run then when the second guess is input, it should move to the "else" statement with the previous guess variables. I tried moving around the variables, changed orders of if/else, and thought maybe it's the placement of index++. Nothing is working. Not sure if something is wrong with my variables , arrays, or the syntax of my if/else statements.
tl;dr: when the program is run only the "else" portion of the nested if-else statement is run. Not sure how to fix… I've gone through my code a number of times. The syntax, the arrays, and variables. Uncertain what's wrong.
You JS has if (index = 0). This should be if (index === 0).
Additionally, you need to cast the value of your input field to a number. You can do this using:
var guess = +$("#guess").val(); // + cast as a number
More syntax errors:
prevguess = guessArray[i - 1] --> prevguess = guessArray[index - 1];
Here is a partial working Fiddle. I ran through some scenarios, and the fiddle really only works if you give the application the right answer. The code has many syntax errors, bad refs and calculations. I would suggest opening the console or a debugger, identifying those issue, and fixing them.
Here is a Fully Functional Demo.

What does while (i --> 0) mean?

I apologize if this a stupid question, but I cannot find the answer anywhere.
How does the following code work? (I realize that it loops over the elements of els)
var i = els.length;
while (i --> 0) {
var el = els[i];
// ...do stuff...
}
I have no idea what --> means. There is no documentation for it. Can someone enlighten me?
It should be read as
i-- > 0
So, what really happens is,
value of i will be checked if it greater than 0, if it is true then control will enter the while block, if it is false while block will be skipped.
Either way, the value of i will be decremented, immediately after the condition is checked.
Its always better to use for loop, when we run a loop with a counter, like this
for (var i = els.length - 1; i >= 0; i -= 1) {
...
}
Please read more about whether ++, -- is okay or not.
It's just weird spacing, should be
while((i--) > 0)
it's just post-decrementing and checking the condition.
There was this humorous answer at the C++ question, but I think it got deleted
while (x --\
\
\
\
> 0) //i goes down to zero!
Or something like that, anyway
So if you had something like
var i=3;
while(i-->0){
console.log(i);
}
it would return
2
1
0
The code should actually be:
while (i-- > 0) {
where the loop will run if the value after the variable i has been decremented is greater than zero.
while (i--> 0)
{
// ...do stuff
}
is same as
while (i>0)
{
i--;
// ...do stuff
}
IMHO we should write simple code rather than clever code because it's not understandable by everyone.
It's just weird spacing. It's same as
while (i-- > 0) {

Prevent javascript from going out of an array

Is there any way I can prevent javascript from dropping an error if I try to go into a non existing array index?
Example: array[-1] would return error and eventually break all my code. How can I let it just return 'undefined' and let my script go on? I can implement an if statement before checking the array (so that if the index is minor than zero or major than the array size it would skip it) but this would be very tedious!
this is my code:
if (grid[j-1][i])
n++;
if (grid[j+1][i])
n++;
if (grid[j][i+1])
n++;
if (grid[j][i-1])
n++;
if (grid[j-1][i-1])
n++;
if (grid[j+1][i+1])
n++;
if (grid[j-1][i+1])
n++;
if (grid[j+1][i-1])
n++;
It is inside of two loops which both sees J and I starting from zero. I don't want to change them and neither writing another if statement (as you can see, there are already too much of them!). Is there any solution?
Thanks!
If you know the measures of your grid, you can put "sentinel cells" around it.
If you add a -1st index to an array x, it does not count to x.length. Putting an additional last element into the list would increment x.length.
I daresay using sentinel cells combined with the arithmetic counting algorithms mentioned by d_inevitable would be the fastest solution, since it would not involve branches. You even can omit the !! because true will evaluate to 1 and false to 0 in an equalization.
Update:
Do not use index -1. Its an awful lot slower that normal array indexes. See http://jsperf.com/index-1.
You could use ||, which muffles errors, e.g.:
(grid[j-1] || [])[i] || false
(I haven't tested this, but it should work)
Edit: updated based on am not i am's suggestion
A less tedious way while still using ifs would be checking the first index if it's defined:
if (typeof grid[j-1] != "undefined" && grid[j-1][i])
You could create a function to do the checks:
function getArrayValue(arr,key) {
if( key < 0 || key >= arr.length) return null;
return arr[key];
}
But really you should be avoiding out-of-bounds keys anyway.
I would do this:
for(m = Math.max(j-1,0) ; m <= Math.min(j+1,grid.length-1) ; m++)
for (p = Math.max(i-1,0) ; p <= Math.min(i+1, grid[m].length-1) ; p++)
n += !(m == j && p == i) && !!grid[m][p];
How about this for your solution?
for (dj = -1; dj <= 1; ++dj) {
for (di = -1; di <= 1; ++di) {
if ((dj || di) && grid[j+dj] && grid[j+dj][i+di]) {
n++;
}
}
}
If you refactor all those ifs into a single loop like the above, then having to do the extra conditional is not so bad.

Categories

Resources