Incrementing and padding in Javascript

Viewed 98

I'm trying to write an app that allows a user to increment a series of numbers. When I let i = 1, all is well and my sequence output is perfect. Unfortunately, the series will rarely start with 1, it will start with a user defined number. And that's where I'm getting hung up:

const totalEl = document.querySelector('#total');
const perEl = document.querySelector('#per');
const numEl = document.querySelector('#num');
const calculateEl = document.querySelector('#calculate');
const outputEl = document.querySelector('#output');

calculateEl.addEventListener('click', onCalculateButtonClick);

function onCalculateButtonClick() {
  const total = Number(totalEl.value);
  const numPerRoll = Number(perEl.value);
  const num = Number(numEl.value);
  const numPerBatch = numPerRoll * 2;


  const batches = Math.ceil(total / numPerBatch);
  let output = `Total batches: ${batches}\nTotal rolls: ${batches * 2}\n\n`;

  for (let i = 1; i <= batches; i++) {
    output += `Batch ${i}A -- ${(numPerBatch * (i - 1)) + 1}-${(numPerBatch * (i - 1)) + numPerRoll}\n`;
    output += `Batch ${i}B -- ${(numPerBatch * (i - 1)) + numPerRoll + 1}-${numPerBatch * i}\n`;
  }

  outputEl.innerText = output;
}
body {
  font-family: system-ui;
  background: linear-gradient(to bottom, azure, #a4ffff);
  color: black;
  height: 100vh;
  margin: 0;
  display: grid;
  }
<h1 style="text-align: center;"> Generator 0.1 </h1>
<p style="text-align: center;">Please enter your values in the dashboard<br> Note: in order to print a complete 2-up batch, the order may be rounded up</p>

<div><label>Order total <input id="total" value="50000"/></label></div>
<div><label>Total per sequence <input id="per" value="1500"/></label></div>
<div><label>Starting number <input id="num" value="1"/></label></div>
<div><label>Prefix <input id="prefix" value="ABC"/></label></div>

<div><button id="calculate">Calculate</button></div>

<br/>
<div id="output"></div>

How do I let i = a user defined number? I'm missing something fundamentally obvious here.

I also need to turn this number into a string (I think) and pad it out to 8 digits... I've tried adding something like this in with lots of hilarity, hair pulling and no results:

let num = i;
let text = num.toString();
let padded = text.padStart(8,"0");

I think what I tried was something like this:

for (let i = num; i <= batches; text = num.toString(); padded = text.padStart(8,"0"); {

I've got a kind of working version of this at codepen: https://codepen.io/swartzfeger/pen/yLjgEpK

More in-depth dump of what I'm trying to accomplish

TL;DR What I need: generate a series of UPC codes that are incremented and split into batches.

Let's say our customer wants 10,000 UPCs, with 1000 UPCs per roll (these are being printed on film and wound onto rolls).

That would be a total of 10 rolls. But each 2x 1000 sequence can be printed at once, printing 2 up. So there are a total of 5 print batches.

That's not the hard part since the example is cut and dried. Most client jobs vary greatly, and we often have to print extra so we're not wasting film -- ie, we always want a full batch and not printing just 1 up for the batch. A partial batch would leave us with 1 roll of empty non-printed film.

ie, an order of 50,000 total, 1500 per sequence. 50k / 3000 per batch leaves us with 16.66 batches. So we would want to round that up to 17. So we would want to bump the print total to 51k (17 x 3000 = 51,000.

What I need to do is create a dashboard where the operator enters the job total (50,000) and the total per sequence (1500). The dashboard would then spit out you're running a job total of 51,000, 17 batches/34 rolls total. It would then give the operator the print breakdown --

  • Batch 1A -- 0000-1500
  • Batch 1B -- 1501-3000
  • Batch 2A -- 3001-4500
  • Batch 2B -- 4501-6000 etc

HOWEVER -- we would rarely begin a sequence with i =1 -- we would be picking up from where our last job ended, so the user would enter the new beginning sequence number. So I wouldn't be incrementing from 0 or 1, but an arbitrary number like 6293.

AND -- these numbers are of going to be a fixed length (usually 8 digits). So a starting sequence number of 6293 would have to be padded out to 00006293.

2 Answers

Make a small utility function like so:

const z = (number, size) => number.toString().padStart(size, "0");

Then assign some variables to the function's returns and then interpolate those variables into the template literals like so:

let A1 = z(AB, 8);
let A2 = z(AB + (now / 2) - 1, 8);
let B1 = z(AB + (now / 2), 8);
let B2 = z(AB + now - 1, 8);
output += `Batch ${z(i + 1, 2)}A -- ${A1}-${A2}\n`;
output += `Batch ${z(i + 1, 2)}B -- ${B1}-${B2}\n`;

I removed the for loop and added .reduce()

Also, the starting number determined by user is already num in the OP, so just assign that value to the definition of i inside the for loop as well as adding it to the batches value and change the less then OR equal to sign (<=) to less than (<):

for (let i = num; i < batches + num; i++) {...

Details are commented in example

const genForm = document.forms.generator;
const fc = genForm.elements;
const totalFC = fc.total;
const perFC = fc.per;
const numFC = fc.num;
const outputFC = fc.output;

// Utility function for padding zeros
const z = (number, size) => number.toString().padStart(size, "0");

genForm.addEventListener('submit', onCalculate);

function onCalculate(e) {
  e.preventDefault();
  const total = Number(totalFC.value);
  const numPerRoll = Number(perFC.value);
  const init = Number(numFC.value);
  const numPerBatch = numPerRoll * 2;

  const batches = Math.ceil(total / numPerBatch);
  let output = `Total batches: ${batches}\nTotal rolls: ${batches*2}\n\n`;

  // Generate an array of batches
  const totalBatches = [...new Array(batches)].fill(numPerBatch);
  /**
   * On each iteration of totalBatches by .reduce()...
   * >AB< is the accumulator, it will keep track of it's own value
   * >now< is the current value (>numPerBatch<)
   * >AB< starts as >init<, defaulting to 1
   * >A1< - >A2< is >numPerRoll<
   * >B1< - >B2< is >numPerRoll<
   * >output< is rendered to display ranges A and B
   * >now< is added to >AB<
   * The returned value isn't used but needs to be defined so
   * that the accumulator returns on the next iteration increased
   */
  const final = totalBatches.reduce((AB, now, idx) => {
    let A1 = z(AB, 8);
    let A2 = z(AB + (now / 2) - 1, 8);
    let B1 = z(AB + (now / 2), 8);
    let B2 = z(AB + now - 1, 8);
    output += `Batch ${z(idx + 1, 2)}A -- ${A1}-${A2}\n`;
    output += `Batch ${z(idx + 1, 2)}B -- ${B1}-${B2}\n`;
    return AB + now;
  }, init);

  outputFC.innerText = output;
}
html {
  font: 300 2ch/1.15 'Segoe UI'
}

body {
  min-height: 100vh;
  margin: 0 auto;
  background: linear-gradient(to bottom, azure, #a4ffff);
}

form {
  display: flex;
  justify-content: center;
  align-items: center;
  padding-top: 20px;
}

fieldset {
  position: relative;
  width: min(70vw, 500px);
}

input,
button {
  font: inherit;
}

input {
  width: 12rem;
}

button {
  position: absolute;
  right: 30px;
  cursor: pointer;
}

label {
  display: flex;
  justify-content: space-between;
  margin: 0 20px 15px 0;
}

label sup {
  align-self: flex-start;
}

legend {
  font-size: 1.5rem;
}

legend b,
[type="number"],
output {
  font-family: Consolas;
}

[type="number"] {
  text-align: right;
}

.note {
  margin-left: 0.5ch;
  font-size: 0.75rem;
  text-indent: -1ch;
}

sup {
  color: tomato
}
<form id="generator">
  <fieldset>
    <legend><b>❚❘❘❙⦀</b>Generator <b>0.1❚⦀⦀∥❙❙❘❘❘⦀∥</b></legend>
    <p>Please enter your values in the dashboard</p>
    <label><span>Order total<sup>⚝</sup></span> <input id="total" type="number" value="50000"/> 
 </label>
    <label>Total per sequence <input id="per" type="number" value="1500"/></label>
    <label>Starting number <input id="num" type="number" value="1"/></label>
    <label>Prefix <input id="prefix" placeholder="ABC"/></label>

    <button>Calculate</button>
    <p class='note'><sup>⚝</sup>Note: in order to print a complete 2-up batch, <br>the order may be rounded up.</p>
    <output id="output"></output>
  </fieldset>
</form>

The const z utility @zer00ne added cleaned things up for padding the digits

const z = (number, size) => number.toString().padStart(size, "0");

The user defined number sets the starting number of the sequence, and size determines the length in digits of the sequence. The "0" determines what the sequence will be padded with to reach the required final length.

ex, the user defines '123' as the starting the number of the sequence; const z turns this into '00000123' (an 8-digit sequence)

Defining the value >now< for the accumulator/iteration made things work flawlessly.

By using AB + now, the batches were able to increment properly:

// Generate an array of batches

const totalBatches = [...new Array(batches)].fill(numPerBatch);

// * On each iteration of totalBatches by .reduce()...
// * >AB< is the accumulator, it will keep track of it's own value
// * >now< is the current value (>numPerBatch<)
// * >AB< starts as >init<, defaulting to 1
// * >A1< - >A2< is >numPerRoll<
// * >B1< - >B2< is >numPerRoll<
// * >output< is rendered to display ranges A and B
// * >now< is added to >AB<
// * The returned value isn't used but needs to be defined so
// * that the accumulator returns on the next iteration increased

const final = totalBatches.reduce((AB, now, idx) => {
let A1 = z(AB, 8);
let A2 = z(AB + now / 2 - 1, 8);
let B1 = z(AB + now / 2, 8);
let B2 = z(AB + now - 1, 8);

and for the final output:

output += `Batch ${z(idx + 1, 2)}A | ${prefix} ${A1} - ${prefix} ${A2}\n`;
output += `Batch ${z(idx + 1, 2)}B | ${prefix} ${B1} - ${prefix} ${B2}\n`;
return AB + now;
}, init);

outputEl.innerText = output;
}
Related