Jquery each loop how to add 3 then skip 5 etc

Hello,

I have a table with 52 rows that represent 52 weeks.
a user can fill in a start week and a end week (ex: start: 5 end: 15) also a user can fill in how many product he was to add and if needed a interval (ex: 3 products skip 5 weeks 3 product again skip 5 weeks etc…)

i am really stuck on the last part and i know dont know how to achieve it.

Fiddle: JSFiddle

Here is my code what i got so far

HTML (note that the weeks should )

 <div>
        <input type="text" name="start_week" placeholder="start week" />
        <input type="text" name="end_week" placeholder="end week" />
        <br /><br />
        <input type="text" name="add" placeholder="Add" />
        <input type="text" name="interval" placeholder="Interval" />
        <a href="#" class="submit">Add</a>
    </div>
    
    <table class="product_table">
        <thead>
            <tr>
                <td width="5%"><strong>Week</strong></td>
                <td width="20%"><strong>Product</strong></td>
            </tr>        
        </thead>
        <tbody>
            <tr>
                <td>1</td>
                <td></td>
            </tr>
            <tr>
                <td>2</td>
                <td></td>
            </tr> 
            <tr>
                <td>3</td>
                <td></td>
            </tr> 
            <tr>
                <td>4</td>
                <td></td>
            </tr> 
            <tr>
                <td>5</td>
                <td></td>
            </tr> 
          etc... till week 52
        </tbody>
    </table>

Jquery:

$(".submit").click(function(){
    var s_week         = $("input[name='start_week']").val();
    var e_week         = $("input[name='end_week']").val();
    
    var add            = parseInt($("input[name='add']").val());
    var interval       = parseInt($("input[name='interval']").val());

    $(".product_table tbody tr td:nth-child(2)").each(function(i) {
        if(i+1 >= s_week && i+1 <= e_week) {
            $(this).append("test");
        }
        
        
       
    });
   
});

Hi,

You could use the modulus operator (which checks the remainder of a division) to create some kind of step function.

This should get you started:

<input type="text" id="start" placeholder="Start" />
<input type="text" id="end" placeholder="End" /><br>
<input type="text" id="add" placeholder="Add" />
<input type="text" id="step" placeholder="Interval" /><br>
<button>Calculate</button>
<div id="result"></div>

and:

$("button").on("click", function(){
  var start = Number($("#start").val()),
      end = Number($("#end").val()),
      add = Number($("#add").val()),
      step = Number($("#step").val()),
      result = [],
      i;
    
    if (step === 0){
        step = 1;
    }
    
    for (i=1; i<end+1; i++){
        var res;
        if (i % step === 0){
          res = "Week " + i + ": " + add + " items<br>";
        } else {
          res = "Week " + i + ": nothing<br>";
        }
        result.push(res);
    }
    
    $("#result").html(result);
});

Demo

Hi thank you very much for taking time to answer.
It should be the other way arround now it skips 5 and adds 2 but it needs to add 2 then skip 5 add 2 then skip 5 stc… ofcourse depending on wha the user fills in.
And it adds 2 in 1 week but what i need is 1 in 2 weeks and then skip 5 weeks.
Example: 1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0 etc…
Is there a way to reverse the modulus function ?

Whaaaaat? :smile:

Let’s take a simple example.

If I enter:

Start: 1
End: 20
Add: 2
Step: 2

What do you desire the output to be?

Hello im sorry if its not clear, my english is not my strongest point i try to be clear as possible
The desired output should look like:

week 1: 1 item
week 2: 1 item
week 3: nothing
week 4: nothing
week 5: 1 item
week 6: 1 item
week 7: nothing
week 8: nothing
week 9: 1item
week 10: 1item
week 11: nothing
week 12: nothing
etc…

The following sample code provides the desired end result for the week numbers.

var order = {start: 1, end: 20, add: 2, step: 2},
    i, weekEnd;

for (i = order.start; i <= order.end; i += (order.add + order.step)) {
    weekEnd = Math.min(i + (order.add - 1), order.end);
    for (j = i; j <= weekEnd; j += 1) {
        console.log(j);
    }
}
// 1, 2, 5, 6, 9, 10, 13, 14, 17, 18
2 Likes

So something like this:

$("button").on("click", function(){
  var start = Number($("#start").val()),
      end = Number($("#end").val()),
      add = Number($("#add").val()),
      step = Number($("#step").val()),
      weeks = end - start,
      total = 0,
      result = [],
      i;
    
      while(total < weeks){
         for(var j=0; j < add; j++){
           result[total] = "Week " + start + ": Item<br>";
           start++;
           total++;
         }
         for(var k=0; k < step; k++){
             result[total] = "Week " + start + ": Nothing<br>";
             start++;
             total++;
         }        
    }
    
    $("#result").html(result);
});

new demo

Disclaimer: this is horrible code and it doesn’t work perfectly. I just want to make sure I understood your problem.

Edit:

I see Paul beat me to it. I was going to delete my reply, but I’ll leave it in case it helps any.

1 Like

Hi Pullo, this is exacly what i want. Thank youre very much for taking time to answer my question.
Im very new to javascript and i still dont understand what you did but it works.

Paul thank you very much for this clean example and it did the job aswell.
i got 1 last question bacause i want to learn from this. could you explain what you did and how it works. i want to understand it and not just copy paste

Yes of course.

I started with a simpler version that gives the start of each order section.

var order = {start: 1, end: 20, add: 2, step: 2},
    i;

for (i = order.start; i <= order.end; i += (order.add + order.step)) {
    console.log(i);
}
// 1, 5, 9, 13, 17

Now that we have a good starting point, it’s an easy matter of using order.add to arrive at desired subsequent numbers.

var order = {start: 1, end: 20, add: 2, step: 2},
    i, weekEnd;

for (i = order.start; i <= order.end; i += (order.add + order.step)) {
    weekEnd = i + order.add;
    for (j = i; j < weekEnd; j += 1) {
        console.log(j);
    }
}
// 1, 2, 5, 6, 9, 10, 13, 14, 17, 18

After that comes testing, which reveals a few issues that need to be accounted for.

First of all what happens when the number of added orders exceeds the total number of desired orders?

var order = {start: 1, end: 21, add: 2, step: 2},
...
// 1, 2, 5, 6, 9, 10, 13, 14, 17, 18, 21, 22

That’s not right. We want order.end to take precendence, not the value with order.add added to it. The easy way to achieve that is to take the smaller of the two.

var order = {start: 1, end: 21, add: 2, step: 2},
...
    weekEnd = Math.min(i + order.add, order.end);
...
// 1, 2, 5, 6, 9, 10, 13, 14, 17, 18

Which reveals another issue. The last week number in this test should be 21, but it’s not showing. This can be achieved only if j is checked to be less than or equal to the value of weekEnd.

    for (j = i; j <= weekEnd; j += 1) {
...
// 1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15, 17, 18, 19, 21

Wow, that’s really wrong, but it’s easy to see why. It’s counting 3 additions now instead of the expected 2. Adding the “or equal to” part means that order.add is counting one too many. Starting at 5 we currently have an end of 5+2. When using “or equal to” we want that instead to just be 5+1 instead.

Removing one from order.add is what we need for good correct behaviour, and adding brackets around it helps to inform us what it’s for.

var order = {start: 1, end: 21, add: 2, step: 2},
...
      weekEnd = Math.min(i + (order.add - 1), order.end);
...
// 1, 2, 5, 6, 9, 10, 13, 14, 17, 18, 21

No other testing shows further improvements that are reasonably required, so it’s good to go.

Putting it all together we end up with:

var order = {start: 1, end: 20, add: 2, step: 2},
    i, weekEnd;

for (i = order.start; i <= order.end; i += (order.add + order.step)) {
    weekEnd = Math.min(i + (order.add - 1), order.end);
    for (j = i; j <= weekEnd; j += 1) {
        console.log(j);
    }
}

Other things can be done to improve it, such as extracting out well named functions for each loop, and using more expressive techniques instead of just for loops, but that can be extra for later :smile:

1 Like

Hi Paul, Thank you very much again !

I ended up with this code:

$(".sub_product_toevoegen").click(function(e) {
    e.preventDefault();
			
var product 	= $("select[name='select_product'] option:selected").val(),
start_week 	= Number($("input[name='start_week']").val()),
eind_week 	= Number($("input[name='eind_week']").val()),
add 		= Number($("input[name='add']").val()),
interval 	= Number($("input[name='interval']").val());

			
								

var order = {start: start_week, end: eind_week, add: add, step: interval},
i, weekEnd;

if(add == "" || interval == "") {
  	for(i = start_week; i <= eind_week; i++) {
		$(".tabs .nieuwe_offerte_table tbody tr td a.product_toevoegen").eq(i-1).append(product);
	}
}
else {
	for (i = order.start; i <= order.end; i += (order.add + order.step)) {
		weekEnd = Math.min(i + (order.add - 1), order.end);

		   for (j = i; j <= weekEnd; j += 1) {
			$(".tabs .nieuwe_offerte_table tbody tr td a.product_toevoegen").eq(j-1).append(product);
		    }
	}
}			
});

It’s great to see that you got things going.

As an example, here is a complete rewrite using more modern techniques, that can also help to make the code easier to understand.

The Array.from() method is new one in ES6 that lets us create an array of values, which can be used so that we can create rangedArray(5) which gives us [0, 1, 2, 3, 4]

We can use it in today’s browsers too by using this Array.from() polyfill

function rangedArray(size) {
    return Array.from(new Array(size), function (x, i) {
        return i;
    });
}

var weeks = rangedArray(20);
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

Instead of using just zero-based indexes, we can offset the values from the start number:

function offsetFromStart(value, i) {
    return order.start + i;
}
...
return Array.from(new Array(20), offsetFromStart);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

An inclusive range will also give us just the values that we need, for a which a small function can help to encapsulate the details.

function rangeSizeInclusive(start, end) {
    // Being inclusive, we also count the start and end values.
    // As an example, the range from 1 to 10 is 10.
    return end - start + 1;
}

var rangeSize = rangeSizeInclusive(order.start, order.end);
return Array.from(new Array(rangeSize), offsetFromStart);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

Now we can put this in to a rangedArray function which allows us to create a full array of weeks from the start to the end, in a more expressive manner:

function rangedArray(size, arrayValueHandler) {
    return Array.from(new Array(size), arrayValueHandler);
}

weeks = rangedArray(rangeSize, offsetFromStart),
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

From there, we can use the values of add and step to filter the weeks further.

With an add of 2 and a step of 2, we have a total period of 4. We want to allow the first two in the add section, and remove the ones in the step area.

With modulus arithmetic on the array index using the total period, we can get 0,1,2,3,0,1,2,3, …
By keeping the ones that are less than the add value, we end up successfully excluding the ones we want to step over.

var fullPeriod = order.add + order.step;

weeks = weeks.filter(function (week, i) {
    // For each modulus period 0f 0,1,2,3,...,0,1,2,3,... we
    // only want to keep those that are in the add section.
    if (i % fullPeriod < order.add) {
        return true;
    }
});

We can further simplify things by pulling i % fullPeriod and the conditional check out into separate sections:

var periodOfWeek = i % fullPeriod;

function isAllowedPeriod(period, limit) {
    return period < limit;
}

return isAllowedPeriod(period, order.add);

We could keep working on this but I think that you get the main idea, of extracting out complexity so that the code is easier to understand and maintain.

The full working code after performing more tidy-ups is as follows: bear in mind that it also needs the Array.from() polyfill too.

The resulting code is longer, but is more expressive and easier to modify and understand.

// Filter weeks based on a regular occurance
// Properties: start, end, add, step

// example usage:
// var weeks = filteredWeeks({start: 1, end: 20, add: 2, step: 2});

// Resulting array: [1, 2, 5, 6, 9, 10, 13, 14, 17, 18]

var filteredWeeks = function (order) {

    function offsetFromStart(value, i) {
        return order.start + i;
    }

    function rangedArray(size, arrayValueHandler) {
        return Array.from(new Array(size), arrayValueHandler);
    }

    function rangeSizeInclusive(start, end) {
        // An inclusive range includes the start and end numbers.
        // As an example, the range from 5 to 10 is 6.
        return end - start + 1;
    }

    function isAllowedPeriod(period, limit) {
        return period < limit;
    }
    
    function filterByAddStepPeriod(week, i) {
        // For each modulus period of 0,1,2,3,...,0,1,2,3,... we only
        // want to keep those that are limited to the add section.

        var fullPeriod = order.add + order.step,
            periodOfWeek = i % fullPeriod;

        return isAllowedPeriod(periodOfWeek, order.add);
    }

    var rangeSize = rangeSizeInclusive(order.start, order.end),
        weeks = rangedArray(rangeSize, offsetFromStart);

    return weeks.filter(filterByAddStepPeriod);
};

var weeks = filteredWeeks({start: 1, end: 20, add: 2, step: 2});
alert('The filtered weeks are: ' + weeks);
1 Like

Hi Paul,

Thank you again. This makes more sense i will inplement this into my application. you and Pullo helped me alot and i wanna thank you for that !

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