Creating User Interactive Applications 2

Creating User Interactive Applications Part 2

Creating Modules for an Interactive Application

We want to be able to create and test modules independent of the main files, index.html and index.mjs. Once we test a module, then we can incorporate this into index.mjs. But, while developing the new module, we want to do this independently.

Creating a new module using CodeSandbox

If you are working in CodeSandbox, you start by adding some new files to your src folder. The following animated gif file shows how you can right-click on the src folder in the Explorer window and select New File. As shown in that animated gif file, create the new files test.html, test.mjs and select_sort.mjs:

create new files


Click on Reload gif to replay animation.

Here is the contents to use for test.html

test.html
<!DOCTYPE html>
<html>
  <head>
    <script type="module" src="./test.mjs"></script>
  </head>
  <body>
    <h1>Test area</h1>
  </body>
</html>

Here is the starting contents for test.mjs:

test.mjs
import { Node } from "./node.mjs";

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", init);
} else {
  init();
}

function init() {
  console.log("init called from test.mjs");
}

With these files, we can now modify the package.json file so that it uses test.html as the main file to load. Here is the new version of package.json:

package.json
{
  "name": "javascript",
  "version": "1.0.0",
  "description": "The JavaScript template",
  "main": "./src/test.html",
  "scripts": {
    "start": "parcel ./src/index.html",
    "build": "parcel build ./src/index.html"
  },
  "devDependencies": {
    "parcel": "^2.0.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^7.2.0"
  },
  "keywords": ["css", "javascript"]
}

The new line is line 5. By adding the "main" key and setting it to "./src/test.html", then test.html will be loaded into the preview. Here is a screen shot of the Preview in CodeSandbox:

test html

As you can see, test.html is loaded and since test.html uses the test.mjs script, that is why the Console shows the message "init called from test.mjs". So, modifying package.json as above now makes it so that we are running test.html which uses test.mjs.

Creating a new module using Vite

If you are using Visual Studio Code, open the sort1 folder. Right-click in the Explorer view and add the files test.html, test.mjs and select_sort.mjs as shown in the next screen shot:

create new files VSCode

The contents for test.html and test.mjs are the same as shown above for using CodeSandbox. These file contents are repeated here:

test.html
<!DOCTYPE html>
<html>
  <head>
    <script type="module" src="./test.mjs"></script>
  </head>
  <body>
    <h1>Test area</h1>
  </body>
</html>
test.mjs
import { Node } from "./node.mjs";

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", init);
} else {
  init();
}

function init() {
  console.log("init called from test.mjs");
}

You don’t have to change any other files to start using test.html in the browser. All you need to do is when you run npm run dev at the command line, then you go to this URL in the browser: localhost:5173/test.html. So, unlike with CodeSandbox, you don’t modify the package.json file.

Creating the select_sort.mjs module

Now that we have our projects set up to test a new module, we can start editing select_sort.mjs. The purpose of creating this module is to create a class called SelectSorter that will be used to perform the Selection Sort on the array of numbers created from the user input. We could do the instructions for the Selection Sort inside of index.mjs. But, we will use a separate module to help keep the lines of code for index.mjs from getting too large. In addition, we could use the select_sort.mjs module in another project that calls for the Selection Sort to be done.

When creating modules, in general, it is a good idea to make it so those modules have as few dependencies as possible. This will make those modules more reusable. This also can make the size of the module from getting too large. To create our select_sort.mjs module, we should first go over the purpose of the module. There are two basic purposes for the select_sort.mjs module. These are:

  • Perform the Selection Sort on an array of numbers.

  • Return all the steps used in performing that selection sort so that the program using this module can recreate those steps.

The Selection Sort

Let’s try to understand how the Selection Sort works. To do this, it will be helpful to have the demo of the Selection sort (Selection Sort Demo) open in another tab in your browser.

The Selection Sort is called by that name because it involves selecting the smallest array element in a given range. When that smallest value is found, it is swapped with the target position. By setting the target position to be at the start of the array, and incrementing this target position all the way to the second to the last array position, the array gets sorted in ascending order. Consider the following array of numbers:

array sort0

We want to get the smallest element of the array into the first position. So, we need to find the position of the smallest element. If we just use our eyes, we can see that the smallest element is the 2 which is in position 1. Remember that for arrays, the first position is position 0. So, we want to swap the array elements in position 0 and position 1, as shown next.

array sort1

The elements have been swapped. So the final position of the first element has been set and is highlighted in red. The next position where we are going to place the next smallest element is highlighted in blue. When we look for the next smallest value, it is in the range of the array to the right of what has already been sorted.

After searching through that range, it is determined that the smallest value in this range is the 4. So, the elements that are to be swapped are shown next, highlighted in green.

array sort2

So, with the 4 swapped into place, the array looks like this. The 2 and 4 are in their final positions so they are highlighted in red. The next smallest value must be found in the range starting at the 17.

array sort3

Since, 17 turns out to be the smallest value in that new range, it stays in place.

array sort4

Now the 2, 4 and 17 are in their final positions. So, we need to find the smallest value in the next range going from 31 to 19.

array sort5

It turns out that 19 is the smallest value in that new range, so 31 and 19 will be swapped and are shown highlighted in green.

array sort6

After that swap, the array is sorted up until the second to the last position. Therefore, the element in the last position is also in its correct sorted position.

array sort7

Coming up with an algorithm to perform the Selection Sort

You would do well to play with the Selection Sort Demo to see if you can predict what will happen before each button click. The thing to keep in mind is that you can scan over an array and find the smallest value, seemingly all at once. The computer can only compare two things at a time. So, when we come up with an algorithm for the selection sort, we need to take this type of comparison into account.

An algorithm is a description in plain text (not code) that describes the instructions a computer must execute to complete some process. Human beings are usually not taught to think in terms of algorithms. Instead, we tend to teach and learn in a heuristic fashion. This means that the learner is expected to use some experience and problem solving skills to learn something. This is different from just following a strict set of instructions, which is what following an algorithm is like. The computer has no real intelligence. Computers can be used to simulate an artificial intelligence, but this requires a lot of human programming. So, while you can ask your friend to buy you a soda to drink and expect your friend to figure out how to do that, you cannot ask the same kind of thing of a computer. If you have children or younger siblings or younger relatives, you could tell them to go to bed and go to sleep. Based on their experience they could figure out what to do. But, if you were instructing a robot that had a computer brain, you might have to do something like this.

Here is a sketch showing the Robot that needs to be told to go to bed:

robot to bed

Algorithm for Robot:

  1. Turn to face due South.

  2. Travel 3.5 feet forward and stop.

  3. Turn to face due East.

  4. Travel 18.4 feet forward and stop.

  5. Turn to face due North.

  6. Travel 5.0 feet forward and stop.

  7. Turn to face due East.

  8. Travel until you hit the bed and lie on the bed.

If you were talking to a human, you could say that the bed is in the last room on the left down the hall, so go lie down on it.

It would probably be very tiresome to give people algorithmic instructions, so we use heuristic instructions instead.

Keep this in mind as we develop the algorithm for the selection sort. That can help you keep things in perspective when you start thinking that coming up with algorithms is hard to do.

algorithm first pass
Start with an array, a, that has n elements.
a[0] refers the the first element in the array.
a[n-1] refers to the last element in the array.
Set i = 0
Compare a[i] with all the subsequent elements and find the smallest element, designated as a[minPos]
Swap a[i] with a[minPos]
Set i = 1
Compare a[i] with all the subsequent elements and find the smallest element, designated as a[minPos]
Swap a[i] with a[minPos]
Continue until array is sorted.

Step 10 is cheating, as that is not an instruction the computer could understand. As a human, you might understand what step 10 means, but as it stands, that is not an instruction suitable for a computer as it is missing too many details.

What we need is something that will repeat a set of instructions like steps 4-6, until the array is sorted. Any time you have repeats, you should be thinking of either a for loop or a while loop. In this case, a for loop is a good choice because the number of repeats is set. That is, the for loop must repeat starting at i = 0, up until i < n - 1, increasing i by 1 each time. We only need to go up until the second to the last position, because once the array is sorted up until that point, the array is already sorted. So, here is the next pass at the algorithm:

algorithm second pass
Start with an array, a, that has n elements.
a[0] refers the the first element in the array.
a[n-1] refers to the last element in the array.
For i = 0 up to i < n - 1, increment i
    select smallest element from a[i] to a[n-1], designate as a[minPos]
    swap a[i] with a[minPos]

This looks more like a valid algorithm, but steps 5 and 6 are missing important details and must be expanded. To select the smallest element, you need to find it. To find the smallest element you need to do a series of comparisons. This means that you must repeatedly do comparisons until you reach the end of the array. This means you need another for loop. Also, to swap array elements actually takes three steps. Here is the next pass at the algorithm.

algorithm third pass
Start with an array, a, that has n elements.
a[0] refers the the first element in the array.
a[n-1] refers to the last element in the array.
For i = 0 up to i < n-1, increment i
    Let min = a[i]
    Let minPos = i
    For j = i+1 up to j = n-1, increment j
        If (a[j] < min)
            min = a[j]
            minPos = j
    Let temp = a[i]
    a[i] = a[minPos]
    a[minPos] = temp

Line 5 will set min to be a[i]. This is the first element in the range of the array that you are searching for the smallest value. Line 6 sets minPos to start off as being i. Lines 7-10 define a for loop that will start at 1 more than the value of i, and go to the end of the array. Line 8 compares a[j] with min. If a[j] is less than min, we have found a smaller value. In that case, lines 9 and 10 update min and minPos. At the end of the for loop, min and minPos will be set so that minPos holds the position of the smallest element in the set range. Lines 11-13 perform the swap of the elements. This takes 3 steps. Line 11 saves the value of a[i] as temp because on line 12, we change the value of a[i] to be what is stored in a[minPos]. If we did not save the original a[i] as temp, then we could not set a[minPos] to the be the original value of a[i].

The first 3 lines of the algorithm are just describing the array. So, the actual code we need to implement will be using lines 4-13.

Keeping this algorithm in mind, we can create our first version of the select_sort.mjs module:

select_sort.mjs
export class SelectSorter {
  constructor(numstring) {
    const nums = numstring.trim().split(",");
    this.num_array = [];
    this.original_array = [];
    this.sort_steps = [];
    for (let num of nums) {
      this.num_array.push(Number(num));
      this.original_array.push(Number(num));
    }
  }

  getOriginalArray() {
    return this.original_array;
  }

  getSortSteps() {
    for (let i = 0; i < this.num_array.length - 1; i++) {
      let min = this.num_array[i];
      let minPos = i;
      for (let j = i + 1; j < this.num_array.length; j++) {
        if (this.num_array[j] < min) {
          min = this.num_array[j];
          minPos = j;
        }
      }
      const temp = this.num_array[i];
      this.num_array[i] = this.num_array[minPos];
      this.num_array[minPos] = temp;
    }
    console.log(this.num_array);
  }
}

Lines 2-9 define the constructor for the SelectSorter class. All that needs to be passed to the constructor is a string that has the array numbers separated by commas. So line 3 uses the split() function to split that string using commas as the delimiters. The trim() function is called right before that to trim off any trailing or leading whitespace in the string. Line 4 creates a class variable called num_array that will be the array that holds the numbers that are converted from the array of strings. The split() function returns an array of strings, not numbers. This is why the for loop on lines 6-8 is needed, to convert to strings into numbers using the Number() function. Line 5 creates another class variable called sort_steps that will hold the information that keeps track of each step of the sorting operation. Those steps will be needed to animate the sort in the main program.

Lines 11-13 define the getOriginalArray() method. This method is just used to return the array of numbers stored in the class variable, num_array.

Lines 15-30 define the first version of the getSortSteps() function. At this stage, we are not going to return the actual sort_steps array. Instead, we will just be implementing the selection sort algorithm we developed above so we can test to see if the array gets properly sorted. You can see that the instructions from 16-28 are the instructions from the algorithm turned into JavaScript code. Note the use of this.num_array.length. The length property stores the number of elements in the array. So, that is equivalent to n from the algorithm. Line 29 will print num_array to the console, so we can see if the array gets sorted correctly.

To test the select_sort.mjs module, we can modify the file test.mjs to make use of the SelectSorter class from the select_sort.mjs module. Here is the next version of test.mjs:

test.mjs
import { Node } from "./node.mjs";
import { SelectSorter } from "./select_sort.mjs";

if (document.readyState === "loading") {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

function init() {
  console.log('init called from test.mjs');
  const numstring = "4,19,2,27,31,6";
  const ss = new SelectSorter(numstring);
  let original_array = ss.getOriginalArray();
  console.log(original_array);
  ss.getSortSteps();
  const ss2 = new SelectSorter("12,7,4,19,16,2,11");
  ss2.getSortSteps();
}

The new lines are 2 and 12-18. Line 2 imports the SelectSorter class from the select_sort.mjs module. Lines 12-18 are just testing the SelectSorter class and whether or not the getSortSteps() method results in a correctly sorted array. Line 2 starts with a comma separated string that will be convered into an array of numbers when the SelectSorter object is constructed on line 3. Line 14 calls the getOriginalArray() method of the SelectSorter class. This should return the original array of numbers that has been created from the comma separated string defined on line 12. Line 15 prints out the array of numbers that is is in the same order as shown in the comma separated string. Line 16 calls the getSortSteps() method. This method will eventually return the steps needed to animate the sort. But, at this point, that method only displays the sorted array of numbers to the console. Lines 17 and 18 do the same thing, for a different string of comma separated numbers.

When this program is run, this is the output shown in the Console:

test select sorter

Hopefully, this allows you to see how testing the module you are creating separately from the project you are using the module for keeps things simple when testing.

The next thing we want to do is actually store the steps needed to animate the sort. This is something that may be tricky to do with your first attempt. The important thing is to create some steps, and then go over those steps to see if they could be used to animate the sort. You will have to visualize how these steps will be used and then see if you need to change the information in those steps and/or add/remove steps. Once you think you have something that will work, then you use the module with the main program to test it. At that point, you have more of an interface to test those steps. That will make it easier to fine tune the steps so that it works the way you want. Like most things in programming, you take some guesses to start, test, then modify based on the results of the test. So, here is the next pass at select_sort.mjs with some steps for performing the sort added in.

select_sort.mjs
export class SelectSorter {
  constructor(numstring) {
    const nums = numstring.trim().split(",");
    this.num_array = [];
    this.original_array = [];
    this.sort_steps = [];
    for (let num of nums) {
      this.num_array.push(Number(num));
      this.original_array.push(Number(num));
    }
  }

  getOriginalArray() {
    return this.original_array;
  }

  getSortSteps() {
    this.sort_steps = [];
    let obj;
    for (let i = 0; i < this.num_array.length - 1; i++) {
      let min = this.num_array[i];
      let minPos = i;
      obj = {};
      obj.step = "set";
      obj.min = min;
      obj.minPos = minPos;
      this.sort_steps.push(obj);
      for (let j = i + 1; j < this.num_array.length; j++) {
        if (this.num_array[j] < min) {
          min = this.num_array[j];
          minPos = j;
        }
      }
      const temp = this.num_array[i];
      this.num_array[i] = this.num_array[minPos];
      this.num_array[minPos] = temp;
      obj = {};
      obj.step = "swap";
      obj.pos1 = i;
      obj.pos2 = minPos;
      this.sort_steps.push(obj);
    }
    //console.log(this.num_array);
    return this.sort_steps;
  }
}

The new lines are 18-19, 23-27, 37-41 and 43-44. Line 18 sets the class variable, sort_steps, to an empty array. Line 19 declares a variable, obj, for the object that will use to hold information about a sort step. Lines 23-26 are used to store the values in obj that we want to store as one of the sort steps. Line 23 makes obj and empty object. Line 24 sets the step property to "set". This is because we want to say that we are setting the values for min and minPos. Lines 25 and 26 store the values for min and minPos as properties for the obj object. Line 27 uses the push() method to append obj to the this.sort_steps array.

Lines 37-41 do the same kind of thing as do lines 23-27. The difference is that on line 38, we set the step property to "swap". This is because we want to say that we are swapping two elements. Lines 39 and 40 store the first position (pos1) and the second position (pos2) as these will be the two positions where we will swap the elements. Line 39 uses the push() method to append obj to the this.sort_step array.

Line 43 just comments out the printing of the sorted array, as we already checked that the sorting is done correctly. Line 44 returns the sort_steps array to the calling program.

Right now, we are not storing any sort steps that have to do with the comparisons needed in the process of finding the min and minPos values. This is to keep the amount of new code short enough so we can test it.

Testing the sort steps

Although we won’t show the animation at this point, we can still test to see if the sort steps we are generating seem to be done correctly. Let’s modify test.mjs to do this.

test.mjs
import { Node } from "./node.mjs";
import { SelectSorter } from "./select_sort.mjs";

if (document.readyState === "loading") {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

function init() {
  console.log('init called from test.mjs');
  const numstring = "4,19,2,27,31,6";
  const ss = new SelectSorter(numstring);
  let original_array = ss.getOriginalArray();
  console.log('original_array:',original_array);
  const sort_steps = ss.getSortSteps();
  for (let i = 0; i < sort_steps.length; i++) {
    const st = sort_steps[i];
    if (st.step === "set") {
      console.log("setting up for comparisons:");
      console.log(`min: ${st.min}, minPos: ${st.minPos}`);
    } else if (st.step === "swap") {
      const pos1 = st.pos1;
      const pos2 = st.pos2;
      console.log(`swapping pos ${pos1} with pos ${pos2}`);
      const temp = original_array[pos1];
      original_array[pos1] = original_array[pos2];
      original_array[pos2] = temp;
    }
  }
  console.log('sorted array:',original_array);
}

The new lines are 15-31. Line 15 adds the string 'original_array' to the output. Line 16 stores the sort steps as sort_steps so that we can iterate over those sort_steps. Lines 17-29 define a for loop that iterates over sort_steps. Line 18 stores sort_steps[i] as st so we can use st in the rest of the instructions inside the for loop. Lines 19-22 check if the st.step is "set". If that is true, then lines 20 and 21 are executed to display "setting up for comparisons:" followed by the min and minPos values. Lines 23-29 are executed if st.step is "swap". So if the type of step is a "swap", lines 23 and 24 save st.pos1 as pos1, and st.pos2 as pos2. Line 25 displays a messages showing which positions are being swapped, and lines 26-28 perform the actual swap of the array elements for the original_array. If these steps are carried out correctly, the array should not be sorted at the end. The following output is from the Console:

test.mjs:11 init called from test.mjs
test.mjs:15 original_array: (6) [4, 19, 2, 27, 31, 6]
test.mjs:20 setting up for comparisons:
test.mjs:21 min: 4, minPos: 0
test.mjs:25 swapping pos 0 with pos 2
test.mjs:20 setting up for comparisons:
test.mjs:21 min: 19, minPos: 1
test.mjs:25 swapping pos 1 with pos 2
test.mjs:20 setting up for comparisons:
test.mjs:21 min: 19, minPos: 2
test.mjs:25 swapping pos 2 with pos 5
test.mjs:20 setting up for comparisons:
test.mjs:21 min: 27, minPos: 3
test.mjs:25 swapping pos 3 with pos 5
test.mjs:20 setting up for comparisons:
test.mjs:21 min: 31, minPos: 4
test.mjs:25 swapping pos 4 with pos 5
test.mjs:31 sorted array: (6) [2, 4, 6, 19, 27, 31]

Line 2 shows the original array before sorting. Lines 3 and 4 show that we are setting up to begin the comparisons by setting min = 4 and minPos = 0. Line 5 shows that after the comparisons are made, pos 0 will be swapped with pos 2. Lines 6-17 do the same kind of thing as lines 3-5, for the rest of the array sorting. Finally, line 18 shows that the array is sorted correctly.

The class variable num_array for the SelectSorter object is used to generate the sort_steps array. To use the sort_steps array to animate the sort, we need to have an array that is in the original order. We will use the getOriginalArray() method to get that array in the original order. This array in the original order will be used to generate an array of Node objects, as that is what will be displayed and animated for the sort demo. Note that in the actual web application, we will not use a for loop to iterate over sort_steps as we want the user interaction to step through the sort steps.

Adding in sort steps for comparisons

So far, the sort steps we generated consist of steps to set up to find the min and minPos values for each iteration of the outer for loop of the selection sort. We also have sort steps that state which positions of the array need to be swapped for the swap step after the inner for loop is completed for the selection sort. But, we have not generated the sort steps used in the comparisons made inside the inner for loop of the selection sort. Let’s modify the select_sort.mjs module so that those comparison steps are also generated.

select_sort.mjs
export class SelectSorter {
  constructor(numstring) {
    const nums = numstring.trim().split(",");
    this.num_array = [];
    this.original_array = [];
    this.sort_steps = [];
    for (let num of nums) {
      this.num_array.push(Number(num));
      this.original_array.push(Number(num));
    }
  }

  getOriginalArray() {
    return this.original_array;
  }

  getSortSteps() {
    this.sort_steps = [];
    let obj;
    for (let i = 0; i < this.num_array.length - 1; i++) {
      let min = this.num_array[i];
      let minPos = i;
      obj = {};
      obj.step = "set";
      obj.min = min;
      obj.minPos = minPos;
      this.sort_steps.push(obj);
      for (let j = i + 1; j < this.num_array.length; j++) {
        obj = {};
        obj.step = "compare";
        obj.pos1 = i;
        obj.pos2 = j;
        if (this.num_array[j] < min) {
          min = this.num_array[j];
          minPos = j;
          obj.minChanged = true;
          obj.min = min;
          obj.minPos = minPos;
        }
        this.sort_steps.push(obj);
      }
      const temp = this.num_array[i];
      this.num_array[i] = this.num_array[minPos];
      this.num_array[minPos] = temp;
      obj = {};
      obj.step = "swap";
      obj.pos1 = i;
      obj.pos2 = minPos;
      this.sort_steps.push(obj);
    }
    //console.log(this.num_array);
    return this.sort_steps;
  }
}

The new lines are 29-32, 36-38 and 40. Line 29 sets obj to be an empty object. Lines 30-32 store the type of step, pos1 and pos2, respectively. Line 36-38 still store the minChanged, min and minPos properties for obj. Note that these are only stored if min has changed. Line 40 uses the push() method to store the compare object to sort_steps.

To test these changes, we need to modify test.mjs. Here is the updated version of test.mjs:

test.mjs
import { Node } from "./node.mjs";
import { SelectSorter } from "./select_sort.mjs";

if (document.readyState === "loading") {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

function init() {
  console.log('init called from test.mjs');
  const numstring = "4,19,2,27,31,6";
  const ss = new SelectSorter(numstring);
  let original_array = ss.getOriginalArray();
  console.log('original_array:',original_array);
  const sort_steps = ss.getSortSteps();
  for (let i = 0; i < sort_steps.length; i++) {
    const st = sort_steps[i];
    if (st.step === "set") {
      console.log("setting up for comparisons:");
      console.log(`min: ${st.min}, minPos: ${st.minPos}`);
    } else if (st.step === "swap") {
      const pos1 = st.pos1;
      const pos2 = st.pos2;
      console.log(`swapping pos ${pos1} with pos ${pos2}`);
      const temp = original_array[pos1];
      original_array[pos1] = original_array[pos2];
      original_array[pos2] = temp;
    } else if (st.step === "compare") {
      const pos1 = st.pos1;
      const pos2 = st.pos2;
      const minChanged = st.minChanged;
      console.log(`comparing pos ${pos1} with pos ${pos2}`);
      if (minChanged === true) {
        console.log(`min changed: new min: ${st.min}, new minPos: ${st.minPos}`);
      }
    }
  }
  console.log('sorted array:',original_array);
}

The new lines are 29-37. Lines 30-36 will be executed if the step type is "compare". Lines 30 and 31 store st.pos1 as pos1 and st.pos2 as pos2. Line 32 stores the value of st.minChanged as minChanged. Note that minChanged is undefined if min has not changed. Lines 34-36 check to see if minChanged is true. If this is the case, then line 35 will print an additional message to the Console saying that min has changed and what the new min and minPos values are. If we run "test.html" now, this is the console output:

test.mjs:11 init called from test.mjs
test.mjs:15 original_array: (6) [4, 19, 2, 27, 31, 6]
test.mjs:20 setting up for comparisons:
test.mjs:21 min: 4, minPos: 0
test.mjs:33 comparing pos 0 with pos 1
test.mjs:33 comparing pos 0 with pos 2
test.mjs:35 min changed: new min: 2, new minPos: 2
test.mjs:33 comparing pos 0 with pos 3
test.mjs:33 comparing pos 0 with pos 4
test.mjs:33 comparing pos 0 with pos 5
test.mjs:25 swapping pos 0 with pos 2
test.mjs:20 setting up for comparisons:
test.mjs:21 min: 19, minPos: 1
test.mjs:33 comparing pos 1 with pos 2
test.mjs:35 min changed: new min: 4, new minPos: 2
test.mjs:33 comparing pos 1 with pos 3
test.mjs:33 comparing pos 1 with pos 4
test.mjs:33 comparing pos 1 with pos 5
test.mjs:25 swapping pos 1 with pos 2
test.mjs:20 setting up for comparisons:
test.mjs:21 min: 19, minPos: 2
test.mjs:33 comparing pos 2 with pos 3
test.mjs:33 comparing pos 2 with pos 4
test.mjs:33 comparing pos 2 with pos 5
test.mjs:35 min changed: new min: 6, new minPos: 5
test.mjs:25 swapping pos 2 with pos 5
test.mjs:20 setting up for comparisons:
test.mjs:21 min: 27, minPos: 3
test.mjs:33 comparing pos 3 with pos 4
test.mjs:33 comparing pos 3 with pos 5
test.mjs:35 min changed: new min: 19, new minPos: 5
test.mjs:25 swapping pos 3 with pos 5
test.mjs:20 setting up for comparisons:
test.mjs:21 min: 31, minPos: 4
test.mjs:33 comparing pos 4 with pos 5
test.mjs:35 min changed: new min: 27, new minPos: 5
test.mjs:25 swapping pos 4 with pos 5
test.mjs:39 sorted array: (6) [2, 4, 6, 19, 27, 31]

The additional lines compared to the last console output are 5-10, 14-18, 22-25, 29-31 and 35-36. These are all the lines that show what positions are being compared in searching for the min value, and also the new min and minPos values if min has changed. Now, we have enough information to animate the sort process.

Switching the main page back to index.html

When we developed the select_sort.mjs module, we switched the main page from index.html, to test.html. Now that we want to make use of the select_sort.mjs module in our main program, we need to switch the main page back to index.html.

If you are using CodeSandbox, this can be done by modifying package.json so that the "main" property is set to "./src/index.html". Here is *package.json":

package.json
{
  "name": "javascript",
  "version": "1.0.0",
  "description": "The JavaScript template",
  "main": "./src/index.html",
  "scripts": {
    "start": "parcel ./src/index.html",
    "build": "parcel build ./src/index.html"
  },
  "devDependencies": {
    "parcel": "^2.0.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^7.2.0"
  },
  "keywords": ["css", "javascript"]
}

When you make these changes you should see that the Preview has switched back to index.html.

If you are using Vite, then all you need to do is change the URL in the browser back to localhost:5173, without the /test.html at the end of the address.

Using the select_sort.mjs module to help animate the Selection Sort Demo

Now that we have created the select_sort.mjs module, we can use this module in our index.mjs to help animate our selection sort demo. Here is the new version of index.mjs:

index.mjs
import { Node } from "./node.mjs";
import { SelectSorter } from "./select_sort.mjs";

if (document.readyState === "loading") {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

function removeChildren(elem) {
  while (elem.childNodes.length > 0) {
    elem.removeChild(elem.childNodes[0]);
  }
}

function createHandlers() {
  const status_label = document.getElementById("status_label");
  const min_label = document.getElementById("min_label");
  const minPos_label = document.getElementById("minPos_label");
  const comparisons_label = document.getElementById("comparisons_label");
  const swaps_label = document.getElementById("swaps_label");
  let comparisons_count = 0;
  let swaps_count = 0;
  const svg_area = document.getElementById("svg_area");
  let ss; // select sorter object
  let sort_steps = [];
  let node_array = [];

  return {

    handleOk() {
      const numbox = document.getElementById("numbox");
      removeChildren(svg_area);
      //let nums = numbox.value.trim().split(",");
      ss = new SelectSorter(numbox.value);
      sort_steps = ss.getSortSteps();
      const original_array = ss.getOriginalArray();
      node_array = [];
      let xStart = 30;
      let yStart = 60;
      for (let i = 0; i < original_array.length; i++) {
        const num = original_array[i];
        const nd = new Node(xStart + i*(50), yStart, 30, 25, num, i);
        node_array.push(nd); // this is from the original unsorted array
        nd.draw(svg_area);
      }
      status_label.textContent = "Getting Started";
      comparisons_label.textContent = comparisons_count;
      swaps_label.textContent = swaps_count;
      console.log(node_array);
    },

    handleCompare() {
      if (!ss) { return; }
      comparisons_count++;
      comparisons_label.textContent = comparisons_count;
      console.log('handleCompare() called');
    },

    handleSwap() {
      if (!ss) {return};
      swaps_count++;
      swaps_label.textContent = swaps_count;
      console.log('handleSwap() called');
    }

  }
}

function init() {
  console.log('init called');
  const handlers = createHandlers();
  const ok_button = document.getElementById("ok_button");
  const compare_button = document.getElementById("compare_button");
  const swap_button = document.getElementById("swap_button");
  ok_button.addEventListener('click', handlers.handleOk);
  compare_button.addEventListener('click', handlers.handleCompare);
  swap_button.addEventListener('click', handlers.handleSwap);
}

The new lines are 2, 25-27, 34-38, 41-42, 44, 50, 54 and 61. Line 2 imports the SelectSorter class from the select_sort.mjs module. Lines 25-27 add some new variables into our factory function. Line 25 declares ss so that it will be used as the name of the SelectSorter object that we construct later on. Line 26 declares sort_steps as an empty array that will later be used to hold the sort steps. Line 27 declares node_array to be an empty array. The node_array array will be used to hold the array of Node objects created to visually represent the array elements.

Line 34 comments out the line previously used to split the comma separated values string. Line 35 constructs the new SelectSorter object from the comma separated value string collected in the input text box. Line 36 obtains the sort steps from the ss object. Line 37 gets the original array of numbers from the ss object. Line 38 makes sure that node_array starts off as an empty array. This is important, as each time we hit the Ok button, we could be reading in a new comma separated value string of numbers to be sorted.

Lines 41-46 define a for loop that iterates over original_array. This means that the numbers used to create the Node objects on line 43, will come from the original unsorted array of numbers. Line 44, stores the newly constructed Node objects inside of node_array. The node_array array, is what we need to manipulate when we animate the sort. Line 50 displays node_array to the Console.

Lines 54 and 61 check to see if ss has already been constructed. If ss has not been constructed, then both the handleCompare() and the handleSwap() functions will return without doing anything. This is an important check, as neither of those two handling functions should execute any instructions if ss has not been constructed.

If you run the main application now and enter some comma separated numbers before hitting Ok, then the Console will show that the Node objects have been created. Here is a screen shot showing this to be the case:

main app ss module0

Now, we need to use the sort_steps array to animate the selection sort.

When we were creating/testing the select_sort.mjs module, we used a for loop to iterate over the sort steps to check to see if the steps were being stored correctly. That was a quick way to test the getSortSteps() method of the SelectSorter class. But, now we want to use sort_steps to control an animation, that will be driven by user input. In this case, that user input will be clicking on the Compare and Swap buttons. So, we won’t be using a for loop here. Instead will will enable/disable the Compare and Swap buttons, so that the user will know which button to click on next for the demo.

This next version of index.mjs will start to run the process needed to animate the selection sort. We will start with a relatively small change as this will involve a new concept:

index.mjs
import { Node } from "./node.mjs";
import { SelectSorter } from "./select_sort.mjs";

if (document.readyState === "loading") {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

function removeChildren(elem) {
  while (elem.childNodes.length > 0) {
    elem.removeChild(elem.childNodes[0]);
  }
}

function createHandlers() {
  const status_label = document.getElementById("status_label");
  const min_label = document.getElementById("min_label");
  const minPos_label = document.getElementById("minPos_label");
  const comparisons_label = document.getElementById("comparisons_label");
  const swaps_label = document.getElementById("swaps_label");
  let comparisons_count = 0;
  let swaps_count = 0;
  const svg_area = document.getElementById("svg_area");
  let ss; // select sorter object
  let sort_steps = [];
  let node_array = [];

  return {

    handleOk(compare_button, swap_button) {
      const numbox = document.getElementById("numbox");
      removeChildren(svg_area);
      //let nums = numbox.value.trim().split(",");
      ss = new SelectSorter(numbox.value);
      sort_steps = ss.getSortSteps();
      const original_array = ss.getOriginalArray();
      node_array = [];
      let xStart = 30;
      let yStart = 60;
      for (let i = 0; i < original_array.length; i++) {
        const num = original_array[i];
        const nd = new Node(xStart + i*(50), yStart, 30, 25, num, i);
        node_array.push(nd); // this is from the original unsorted array
        nd.draw(svg_area);
      }
      status_label.textContent = "Getting ready to find the min value";
      comparisons_label.textContent = comparisons_count;
      swaps_label.textContent = swaps_count;
      console.log(node_array);
      compare_button.disabled = false;
      swap_button.disabled = true;
    },

    handleCompare(compare_button, swap_button) {
      if (!ss) { return; }
      comparisons_count++;
      comparisons_label.textContent = comparisons_count;
      console.log('handleCompare() called');
      compare_button.disabled = true;
      swap_button.disabled = false;
    },

    handleSwap(compare_button, swap_button) {
      if (!ss) {return};
      swaps_count++;
      swaps_label.textContent = swaps_count;
      console.log('handleSwap() called');
      compare_button.disabled = false;
      swap_button.disabled = true;
    }

  }
}

function init() {
  console.log('init called');
  const handlers = createHandlers();
  const ok_button = document.getElementById("ok_button");
  const compare_button = document.getElementById("compare_button");
  const swap_button = document.getElementById("swap_button");
  ok_button.addEventListener('click', () => {
    handlers.handleOk(compare_button, swap_button);
  });
  compare_button.addEventListener('click', () => {
    handlers.handleCompare(compare_button, swap_button);
  });
  swap_button.addEventListener('click', () => {
    handlers.handleSwap(compare_button, swap_button);
  });
}

The new lines are 31, 47, 51-52, 55, 60-61, 64, 69-70 and 82-90. Most of these changes depend on the changes made in lines 82-90. Lines 82-84 are replacing the following lines:

previous line 76
ok_button.addEventListener('click', handlers.handleOk);

Lines 82-84 make it so that the handling function that is called when we click on ok_button is passed the parameters, compare_button and swap_button. There is an important reason for doing this for our application. The init() function is where a lot of initialization instructions take place. So, compare_button and swap_button are defined here and the event handlers for clicking on them is also defined inside init(). This is important because on line 78 we we run createHandlers(), this is where the handling functions become defined. However, we want to be able to control when compare_button and swap_button are enabled/disabled. This will allow our program to control which of those buttons is enabled so that the user clicks on the correct button for a given step in the sorting process. We have previously used a for loop when testing the select_sort.mjs module to step through the sorting process. But, to allow the user to interact with the sort demo, we are not using a for loop. Such a for loop would take away from the interactive experience for the user. The bottom line, is that we want to control which button is enabled and which button is disabled. This will make it easy for the user to click on the correct button during the sorting process. That should also help the user to better understand how the selection sort works.

Passing the references to the buttons the user interacts with as parameters for the handling functions is a way to avoid having to define global variables for those buttons. So, lines 82-90 represent an important way of organizing a more complex web application that includes buttons as part of the user interactivity, without having to resort to global variables.

Lines 85-87 set up the parameter passing for the handling function for the compare_button's click events. Lines 88-90 do the same thing for the swap_button.

Line 31 has been modified so that the compare_button and swap_button parameters are declared. Line 47 changed the status_label content to something more specific to the step. Lines 50 and 51 make it so that the compare_button will be enabled and the swap_button will be disabled. This because when the user clicks on the Ok button, the compare_button should become the only button that is enabled.

Line 55 has been modified to include the compare_button and swap_button parameters. Lines 60 and 61 make it so that the compare_button is disabled and the swap_button is enabled after the user hits the Compare button.

Line 64 has been modified to include the compare_button and swap_button parameters. Lines 69 and 70 make it so that the compare_button is enabled and the swap_button is disabled after the user hits the Swap button.

Note that this is not the actual behavior for those buttons, as the current behavior is not tied to the actual sorting steps. But, it is a quick way to check that we are able to control the enabling/disabling of the Compare and Sort buttons by clicking on them. So, this is just a small incremental step towards animating the sort. It is a very important increment, as this represents another important pattern to avoid using global variables. But, it is still a small incremental step, as this is not the final behavior of clicking on those buttons. You can run the program and check to see that if you enter a comma separated value string of numbers and hit Ok, that you will be able to see that the Compare and Sort buttons will be enabled and disabled as though we were switching from one button to the other.

Modifying handleCompare() to make use of the sort steps

Let’s start to modify the handleCompare() handler so that it starts to take into account the actual sorting steps. One of the things that we need to take into account, is that after all the comparisons are made to the end of the array, the next step will be a swap. This means that we need to look at the current step and the next step. If the next step when doing a compare is a swap, then we need to disable compare_button and enable swap_button. At this point, we will just print out the sort step. This will help us to plan the next modification to index.mjs. But, for this version, just printing out the step is the main thing we want to accomplish.

When we print the step out to the Console, some of that object can be hidden by an ellipsis (…​). So, instead of just using:

console.log(step);

we will use the following:

console.log(JSON.stringify(step));

This will print out a JSON string that shows the entire object. Here is the next version of index.mjs:

index.mjs
import { Node } from "./node.mjs";
import { SelectSorter } from "./select_sort.mjs";

if (document.readyState === "loading") {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

function removeChildren(elem) {
  while (elem.childNodes.length > 0) {
    elem.removeChild(elem.childNodes[0]);
  }
}

function createHandlers() {
  const status_label = document.getElementById("status_label");
  const min_label = document.getElementById("min_label");
  const minPos_label = document.getElementById("minPos_label");
  const comparisons_label = document.getElementById("comparisons_label");
  const swaps_label = document.getElementById("swaps_label");
  let comparisons_count = 0;
  let swaps_count = 0;
  const svg_area = document.getElementById("svg_area");
  let ss; // select sorter object
  let sort_steps = [];
  let node_array = [];
  let step_count = 0;

  return {

    handleOk(compare_button, swap_button) {
      const numbox = document.getElementById("numbox");
      removeChildren(svg_area);
      //let nums = numbox.value.trim().split(",");
      ss = new SelectSorter(numbox.value);
      step_count = 0;
      sort_steps = ss.getSortSteps();
      const original_array = ss.getOriginalArray();
      node_array = [];
      let xStart = 30;
      let yStart = 60;
      for (let i = 0; i < original_array.length; i++) {
        const num = original_array[i];
        const nd = new Node(xStart + i*(50), yStart, 30, 25, num, i);
        node_array.push(nd); // this is from the original unsorted array
        nd.draw(svg_area);
      }
      status_label.textContent = "Getting ready to find the min value";
      comparisons_label.textContent = comparisons_count;
      swaps_label.textContent = swaps_count;
      console.log(node_array);
      const step = sort_steps[step_count];
      console.log(JSON.stringify(step));
      step_count++;  // go to next step
      compare_button.disabled = false;
      swap_button.disabled = true;
    },

    handleCompare(compare_button, swap_button) {
      if (!ss) { return; }
      comparisons_count++;
      comparisons_label.textContent = comparisons_count;
      const step = sort_steps[step_count];
      const next_step = sort_steps[step_count + 1];
      if (next_step.step === "swap") {
        console.log(JSON.stringify(step));
        compare_button.disabled = true;
        swap_button.disabled = false;
      } else {
        console.log(JSON.stringify(step));
      }
      step_count++;
    },

    handleSwap(compare_button, swap_button) {
      if (!ss) {return};
      swaps_count++;
      swaps_label.textContent = swaps_count;
      console.log('handleSwap() called');
      compare_button.disabled = false;
      swap_button.disabled = true;
    }

  }
}

function init() {
  console.log('init called');
  const handlers = createHandlers();
  const ok_button = document.getElementById("ok_button");
  const compare_button = document.getElementById("compare_button");
  const swap_button = document.getElementById("swap_button");
  ok_button.addEventListener('click', () => {
    handlers.handleOk(compare_button, swap_button);
  });
  compare_button.addEventListener('click', () => {
    handlers.handleCompare(compare_button, swap_button);
  });
  swap_button.addEventListener('click', () => {
    handlers.handleSwap(compare_button, swap_button);
  });
}

The new lines are 28, 37, 53-55 and 64-73. Line 28 defines the step_count variable which will keep track of where we are in the sort_steps array. Line 37 sets step_count back to 0, as when we hit the Ok button we are reading in a new string for the array to be sorted. Line 53 gets the first step from sort_steps and stores this as step. Line 54 prints out the step object in JSON.stringified form. Line 55 increments (increases by one) step_count to advance to the next step in sort_steps.

Lines 64-72 are the changes made to the handleCompare() handler. Line 64 gets the current step and line 65 gets the next step from sort_steps. Lines 66-70 check to see if the next step is a "swap". If this is the case, the step is printed out in JSON.stringified form and the compare_button is disabled and the swap_button is enabled. Lines 70-72 will be executed if the next step is not a "swap". In that case line 71 will print the step object out in JSON.stringified form. Line 73 advances to the next step.

Suppose we enter the comma separated value string, "4,13,1,6,22" into the input text box and click Ok. Then, the following shows what will be displayed to the console as we continue to hit compare_button until the "swap" is reached.

console output for entering "4,13,1,6,22"
index.mjs:89 init called
index.mjs:52 (5) [Node, Node, Node, Node, Node]
index.mjs:54 {"step":"set","min":4,"minPos":0}
index.mjs:71 {"step":"compare","pos1":0,"pos2":1}
index.mjs:71 {"step":"compare","pos1":0,"pos2":2,"minChanged":true,"min":1,"minPos":2}
index.mjs:71 {"step":"compare","pos1":0,"pos2":3}
index.mjs:67 {"step":"compare","pos1":0,"pos2":4}
index.mjs:80 handleSwap() called

After printing out node_array on line 2, line 3 shows the initial step in sort_steps. Line 4 shows that postion 0 and position 1 would be compared. This compares the 4 to 13. Line 5 shows that position 0 and position 2 are compared. This compares the 4 to 1. Since 1 is less than 4, the min and minPos values changed. Although line 6 says that position 0 and position 3 would be compared, this is not what is happening. Actually 1 is being compared to 6. On line 7, actually 1 is being compared to what is in position 4. Finally, on line 8, the swap would occur. This points out that our message for the sort steps should be modified. So, we will do that before trying to modify index.mjs any further.

It is fairly common to start developing a module for an application, and find out there are things you need to change about that module later on. This is often when you are using that module within that application. This is because you need to see the module working in an actual application before some issues show up. So, we will modify the select_sort.mjs module, but since it is already functional, we don’t need to use test.html and test.mjs to do this. We can simply see how changes we make to select_sort.mjs will be reflected in running our main application using index.html and index.mjs.
select_sort.mjs
export class SelectSorter {
  constructor(numstring) {
    const nums = numstring.trim().split(",");
    this.num_array = [];
    this.original_array = [];
    this.sort_steps = [];
    for (let num of nums) {
      this.num_array.push(Number(num));
      this.original_array.push(Number(num));
    }
  }

  getOriginalArray() {
    return this.original_array;
  }

  getSortSteps() {
    this.sort_steps = [];
    let obj;
    for (let i = 0; i < this.num_array.length - 1; i++) {
      let min = this.num_array[i];
      let minPos = i;
      obj = {};
      obj.step = "set";
      obj.min = min;
      obj.minPos = minPos;
      this.sort_steps.push(obj);
      for (let j = i + 1; j < this.num_array.length; j++) {
        obj = {};
        obj.step = "compare";
        obj.originalMin = min;
        obj.originalMinPos = minPos;
        obj.comparePos = j;
        obj.compareTo = this.num_array[j];
        if (this.num_array[j] < min) {
          min = this.num_array[j];
          minPos = j;
          obj.minChanged = true;
          obj.min = min;
          obj.minPos = minPos;
        }
        this.sort_steps.push(obj);
      }
      const temp = this.num_array[i];
      this.num_array[i] = this.num_array[minPos];
      this.num_array[minPos] = temp;
      obj = {};
      obj.step = "swap";
      obj.pos1 = i;
      obj.pos2 = minPos;
      this.sort_steps.push(obj);
    }
    //console.log(this.num_array);
    return this.sort_steps;
  }
}

The new lines are 31-34. Line 31 stores the property originalMin as the min value before the comparison. Line 32 stores the property originalMinPos as the minPos value before the comparison. Line 33 stores the property comparePos as the position we are comparing the min with. Line 34 stores the property compareTo as the value that we are comparing min with. These additional properties provide additional information that will allow better content for our status_label.

Here is the new version of index.mjs that uses those additional properties.

index.mjs
import { Node } from "./node.mjs";
import { SelectSorter } from "./select_sort.mjs";

if (document.readyState === "loading") {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

function removeChildren(elem) {
  while (elem.childNodes.length > 0) {
    elem.removeChild(elem.childNodes[0]);
  }
}

function createHandlers() {
  const status_label = document.getElementById("status_label");
  const min_label = document.getElementById("min_label");
  const minPos_label = document.getElementById("minPos_label");
  const comparisons_label = document.getElementById("comparisons_label");
  const swaps_label = document.getElementById("swaps_label");
  let comparisons_count = 0;
  let swaps_count = 0;
  const svg_area = document.getElementById("svg_area");
  let ss; // select sorter object
  let sort_steps = [];
  let node_array = [];
  let step_count = 0;

  return {

    handleOk(compare_button, swap_button) {
      const numbox = document.getElementById("numbox");
      removeChildren(svg_area);
      //let nums = numbox.value.trim().split(",");
      ss = new SelectSorter(numbox.value);
      step_count = 0;
      sort_steps = ss.getSortSteps();
      const original_array = ss.getOriginalArray();
      node_array = [];
      let xStart = 30;
      let yStart = 60;
      for (let i = 0; i < original_array.length; i++) {
        const num = original_array[i];
        const nd = new Node(xStart + i*(50), yStart, 30, 25, num, i);
        node_array.push(nd); // this is from the original unsorted array
        nd.draw(svg_area);
      }
      status_label.textContent = "Getting ready to find the min value";
      comparisons_label.textContent = comparisons_count;
      swaps_label.textContent = swaps_count;
      console.log(node_array);
      const step = sort_steps[step_count];
      console.log(JSON.stringify(step));
      min_label.textContent = step.min;
      minPos_label.textContent = step.minPos;
      step_count++;  // go to next step
      compare_button.disabled = false;
      swap_button.disabled = true;
    },

    handleCompare(compare_button, swap_button) {
      if (!ss) { return; }
      comparisons_count++;
      comparisons_label.textContent = comparisons_count;
      const step = sort_steps[step_count];
      const next_step = sort_steps[step_count + 1];
      let msg = `comparing ${step.originalMin} with ${step.compareTo}`
      if (next_step.step === "swap") {
        console.log(JSON.stringify(step));
        compare_button.disabled = true;
        swap_button.disabled = false;
      } else {
        console.log(JSON.stringify(step));
      }
      if (step.minChanged === true) {
        msg += ", min and minPos are changed.";
        min_label.textContent = step.min;
        minPos_label.textContent = step.minPos;
      } else {
        msg += ", min is unchanged.";
      }
      status_label.textContent = msg;
      step_count++;
    },

    handleSwap(compare_button, swap_button) {
      if (!ss) {return};
      swaps_count++;
      swaps_label.textContent = swaps_count;
      console.log('handleSwap() called');
      compare_button.disabled = false;
      swap_button.disabled = true;
    }

  }
}

function init() {
  console.log('init called');
  const handlers = createHandlers();
  const ok_button = document.getElementById("ok_button");
  const compare_button = document.getElementById("compare_button");
  const swap_button = document.getElementById("swap_button");
  ok_button.addEventListener('click', () => {
    handlers.handleOk(compare_button, swap_button);
  });
  compare_button.addEventListener('click', () => {
    handlers.handleCompare(compare_button, swap_button);
  });
  swap_button.addEventListener('click', () => {
    handlers.handleSwap(compare_button, swap_button);
  });
}

The new lines are 55-56, 68 and 76-83. Lines 55 and 56 set the min_label and minPos_label contents with the starting values.

Line 68 creates a variable, msg that will be used to update status_label. Lines 76-82 form a selection statement based on whether or not step.minChanged is true or not. If step.minChanged is true, then ", min and minPos are changed." is appended to msg. In addition, min_label and minPos_label are updated. If step.minChanged is not true, then ", min is unchanged." is appended to msg. Line 83 updates status_label with the value for msg.

The following animated gif file shows what happens when we run the application with the changes to select_sort.mjs and index.mjs.

compare messages

Click on Reload gif to replay animation.

The string "4,13,1,6,23" is entered into the input text box and the Ok button is hit. That Status shows "Getting ready to find the min value". The value of min is 4 and minPos is 0. When the user clicks on the Compare button, Status changes to "comparing 4 with 13, min is unchanged.". When the Compare button is hit again, Status changes to "comparing 4 with 1, min and minPos are changed." Note that min is set to 1 and minPos is set to 2. Clicking Compare again results in Status changing to "comparing 1 with 6, min is unchanged.". Finally, clicking on Compare results in Status changing to "comparing 1 with 22, min is unchanged.". At that point, the compare_button is disabled and the swap_button is enabled. This will let the user know that the next step will be to swap elements.

Next, we will start to work on the handleSwap() handling function.

Modifying handleSwap() to make use of the sort steps

The handleSwap() handling function is the most complex of the handling functions we have to deal with. This is because this handling function will actually cause the nodes on the screen to be swapped. In addition to swapping the nodes, we will need to update the node_array array. This is because causing the SVG objects to move does not change the elements in node_array. If you recall, node_array is the array of Node objects that is in the original order of the entered numbers for the array to be sorted. When we carried out the selection sort algorithm inside of the select_sort.mjs module, the variable num_array was the actual array of numbers that the sort was performed on. So, when we swap the first min value into position 0, that min value becomes the new element at position 0 for num_array. We need to do the same thing for node_array. Otherwise the sort steps that are based on the num_array elements, will not refer to the correct array elements.

For the lessons that are used to create the selection sort demo, we have to deal with more than one representation of the array that is sorted. One representation, num_array, is an array of numbers that we can apply the selection sort algorithm to. This is the simplest way to apply the selection sort algorithm, as this involves far less code than applying the selection sort to an array of type Node. When we sorted num_array, we also came up with a series of sort steps that can be used to animated the SVG object representation of the array. Those sort steps were generated assuming that we are moving around the actual array elements until the array is sorted. This means that the original indices for those array elements will no longer be correct once they array is sorted (unless the array was already sorted to begin with). So, when we are animating the sort, we need to treat our array of Nodes like we treated num_array so that all the position values we generated in creating the sort steps will be valid. This is another benefit of using separate modules like select_sort.mjs and node.mjs. We can separate out the code that is specific to those modules from each other and from the main program index.mjs. That makes the code easier to create and to maintain. As you have seen, we modified the select_sort.mjs module to make it work better with index.mjs. This was easy to do, because we knew exactly which set of code we had to look at in order to do this. When it comes to programming design and patterns, this lesson is a good example of why using modules is such a good practice.

In order to be able to manipulate and use node_array, we need to have a way of referring to each of the Node objects inside of node_array. This is the reason why the node.mjs module was written so that each Node has its own id value. We can use that id value as an index into the node_array array. But, this will require that we be able to change the id values of a Node object, so that the id can always be treated as the array index. This means we need a function that will be able to reset the id values for each Node in node_array. This will be part of the changes we need to make to index.mjs when we modify the handleSwap() handling function. Here is the next version of index.mjs that starts to make those changes:

index.mjs
import { Node } from "./node.mjs";
import { SelectSorter } from "./select_sort.mjs";

if (document.readyState === "loading") {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

function removeChildren(elem) {
  while (elem.childNodes.length > 0) {
    elem.removeChild(elem.childNodes[0]);
  }
}

function createHandlers() {
  const status_label = document.getElementById("status_label");
  const min_label = document.getElementById("min_label");
  const minPos_label = document.getElementById("minPos_label");
  const comparisons_label = document.getElementById("comparisons_label");
  const swaps_label = document.getElementById("swaps_label");
  let comparisons_count = 0;
  let swaps_count = 0;
  const svg_area = document.getElementById("svg_area");
  let ss; // select sorter object
  let sort_steps = [];
  let node_array = [];
  let step_count = 0;

  function getNodeById(id) {
    for (let i = 0; i < node_array.length; i++) {
      const nd = node_array[i];
      if (Number(nd.g.id) === id) {
        return nd;
      }
    }
  }

  function resetIds() {
    for (let i = 0; i < node_array.length; i++) {
      node_array[i].g.setAttribute("id", i);
    }
  }

  return {

    handleOk(compare_button, swap_button) {
      const numbox = document.getElementById("numbox");
      removeChildren(svg_area);
      //let nums = numbox.value.trim().split(",");
      ss = new SelectSorter(numbox.value);
      step_count = 0;
      sort_steps = ss.getSortSteps();
      const original_array = ss.getOriginalArray();
      node_array = [];
      let xStart = 30;
      let yStart = 60;
      for (let i = 0; i < original_array.length; i++) {
        const num = original_array[i];
        const nd = new Node(xStart + i*(50), yStart, 30, 25, num, i);
        node_array.push(nd); // this is from the original unsorted array
        nd.draw(svg_area);
      }
      status_label.textContent = "Getting ready to find the min value";
      comparisons_label.textContent = comparisons_count;
      swaps_label.textContent = swaps_count;
      console.log(node_array);
      const step = sort_steps[step_count];
      console.log(JSON.stringify(step));
      min_label.textContent = step.min;
      minPos_label.textContent = step.minPos;
      step_count++;  // go to next step
      compare_button.disabled = false;
      swap_button.disabled = true;
    },

    handleCompare(compare_button, swap_button) {
      if (!ss) { return; }
      comparisons_count++;
      comparisons_label.textContent = comparisons_count;
      const step = sort_steps[step_count];
      const next_step = sort_steps[step_count + 1];
      let msg = `comparing ${step.originalMin} with ${step.compareTo}`
      if (next_step.step === "swap") {
        console.log(JSON.stringify(step));
        compare_button.disabled = true;
        swap_button.disabled = false;
      } else {
        console.log(JSON.stringify(step));
      }
      if (step.minChanged === true) {
        msg += ", min and minPos are changed.";
        min_label.textContent = step.min;
        minPos_label.textContent = step.minPos;
      } else {
        msg += ", min is unchanged.";
      }
      status_label.textContent = msg;
      step_count++;
    },

    async handleSwap(compare_button, swap_button) {
      if (!ss) {return};
      swaps_count++;
      swaps_label.textContent = swaps_count;
      const step = sort_steps[step_count];
      console.log(JSON.stringify(step));
      const nd1 = getNodeById(step.pos1);
      const nd2 = getNodeById(step.pos2);
      if (step.pos1 !== step.pos2) {
        await nd1.swap(nd2, 700);
        const temp = node_array[step.pos1];
        node_array[step.pos1] = node_array[step.pos2];
        node_array[step.pos2] = temp;
        resetIds();
      } else {
        console.log("node already in correct place");
      }
      if (step_count < sort_steps.length - 1) {
        step_count++;  // advance to "set" step
        const set_step = sort_steps[step_count];
        console.log(JSON.stringify(set_step));
        status_label.textContent = `comparing ${set_step.min} with rest of array.`;
        min_label.textContent = set_step.min;
        minPos_label.textContent = set_step.minPos;
        step_count++;  // advance to "compare" step
        compare_button.disabled = false;
        swap_button.disabled = true;
      } else {
        status_label.textContent = "All done";
        compare_button.disabled = true;
        swap_button.disabled = true;
      }
    }

  }
}

function init() {
  console.log('init called');
  const handlers = createHandlers();
  const ok_button = document.getElementById("ok_button");
  const compare_button = document.getElementById("compare_button");
  const swap_button = document.getElementById("swap_button");
  ok_button.addEventListener('click', () => {
    handlers.handleOk(compare_button, swap_button);
  });
  compare_button.addEventListener('click', () => {
    handlers.handleCompare(compare_button, swap_button);
  });
  swap_button.addEventListener('click', () => {
    handlers.handleSwap(compare_button, swap_button);
  });
}

The new lines are 30-37, 39-43, 102 and 106-133. Lines 30-37 define the getNodeById() function. This function is define inside of our factory function, createHandlers(). The getNodeById() function is used to be able to return a Node object from the node_array array. That is why we define this function within the createHandlers() function, as this is where node_array is defined and is directly accessible. We call this type of function a helper function, as it is just used to help the handling functions get their job done. Lines 31-36 iterate over node_array. Line 32 just defines a const called nd for convenience. So, on line 33 when we are getting the id of the Node to compare with the passed id, the command takes less code. Note on line 33, that nd.g.id is a string. So, we need to use the Number() function to convert that string into a number to compare it with the passed id. When there is a match, line 34 returns the matching Node object to the calling program. The getNodeById() function is called on lines 109 and 110 inside the handleSwap() handling function. This allows obtaining the nodes that will be swapped.

Lines 39-43 define the helper function called, resetIds(). Lines 40-42 define a for loop that iterates over node_array to set the id of the node to match the index of the array. This is a very important function as it allows the sort steps created with the getSortSteps() method of the SelectSorter class, to access the correct Node from node_array. The resetIds() function is called on line 115, to reset all the id values for node_array after the swap has been performed for node_array.

Line 102 has been modified to use async as there will be instructions that are executed inside the handleSwap() handling function that we need to await.

Line 106-133 are the new lines inside the handleSwap() handling function. Line 106 gets the current sort_step and stores this as step. Line 107 prints out the JSON.stringified representation of step so that we can see all the properties of step. This will help us to determine the correct properties of step to use in the subsequent instructions. Line 108 gets the node at step.pos1 and stores this as nd1. Line 109 gets the node at step.pos2 and stores this as nd2. Lines 110-118 define a selection statement that will execute lines 111-115 if the positions to be swapped are different. If the positions are the same, the node is already in the correct place, so line 117 will just print a simple message to say this is the case. Line 111 will perform the swap of the SVG objects. We use await so that this instruction completes before line 112 can be executed. On line 111, we use a delay of 700 msec. This will allow about 0.7 seconds for the swap animation to complete. Lines 112-114 perform the swap of the node_array elements. Remember that we need to do this every time the SVG objects are swapped. By swapping the node_array elements on line 112-114 and then calling resetIds() on line 115, the nodes in node_array will have updated id values that match the index of node_array. This is very important, as the sort steps assume that when a swap occurs, the num_array elements are actually swapped. When the num_array elements are swapped, the index values of the swapped elements change. So, we are just reflecting this change using lines 112-115. Without doing this, the animation of the SVG objects will be incorrect.

Lines 119-133 define another selection statement. When we perform a swap, it could be that there are still sort steps to be performed. If that is the case, then step_count will be less than the total number of steps minus 1. This is the first case of that selection statement. The second case on lines 129-133, is when there are no more sort steps after this swap.

Lines 120-128 will be executed if there are still more sort steps after this swap. Line 120 increments step_count. That will advance to a "set" step. Line 121 gets that sort step and stores this as set_step. Line 122 prints out set_step in JSON.stringified form. Line 123 update the status_label contents just stating that we are comparing the new min with the rest of the array. Later on, we will change the status_label contents to say what value we are comparing min with. Lines 124 and 125 update the min_label and minPos_label contents, respectively. Line 126 will increment step_count again. This will advance to the "compare" step after the "set" step. Lines 127 and 128 will enable compare_button and disable swap_button.

Lines 130-132 are executed if the swap was the last sort step. So, line 130 sets the status_label contents to "All done", and lines 131 and 132 disable both compare_button and swap_button. That will make it clear to the user that the sort process is complete.

Now that we are handling the Ok button, the Compare button and Swap button, it is a good idea to look at the Console output that is being produced when the program runs. This could show a need for more console.log() statements, and can also be used to check if the status_label is being updated correctly.

Here is what the console output looks like for an array created from "4,13,1,6,22".

console output for "4,13,1,6,22"
index.mjs:140 init called
index.mjs:67 (5) [Node, Node, Node, Node, Node]
index.mjs:69 {"step":"set","min":4,"minPos":0}
index.mjs:89 {"step":"compare","originalMin":4,"originalMinPos":0,"comparePos":1,"compareTo":13}
index.mjs:89 {"step":"compare","originalMin":4,"originalMinPos":0,"comparePos":2,"compareTo":1,"minChanged":true,"min":1,"minPos":2}
index.mjs:89 {"step":"compare","originalMin":1,"originalMinPos":2,"comparePos":3,"compareTo":6}
index.mjs:85 {"step":"compare","originalMin":1,"originalMinPos":2,"comparePos":4,"compareTo":22}
index.mjs:107 {"step":"swap","pos1":0,"pos2":2}
index.mjs:122 {"step":"set","min":13,"minPos":1}
index.mjs:89 {"step":"compare","originalMin":13,"originalMinPos":1,"comparePos":2,"compareTo":4,"minChanged":true,"min":4,"minPos":2}
index.mjs:89 {"step":"compare","originalMin":4,"originalMinPos":2,"comparePos":3,"compareTo":6}
index.mjs:85 {"step":"compare","originalMin":4,"originalMinPos":2,"comparePos":4,"compareTo":22}
index.mjs:107 {"step":"swap","pos1":1,"pos2":2}
index.mjs:122 {"step":"set","min":13,"minPos":2}
index.mjs:89 {"step":"compare","originalMin":13,"originalMinPos":2,"comparePos":3,"compareTo":6,"minChanged":true,"min":6,"minPos":3}
index.mjs:85 {"step":"compare","originalMin":6,"originalMinPos":3,"comparePos":4,"compareTo":22}
index.mjs:107 {"step":"swap","pos1":2,"pos2":3}
index.mjs:122 {"step":"set","min":13,"minPos":3}
index.mjs:85 {"step":"compare","originalMin":13,"originalMinPos":3,"comparePos":4,"compareTo":22}
index.mjs:107 {"step":"swap","pos1":3,"pos2":3}
index.mjs:117 node already in correct place

Let’s look at the console output on lines 3 and 4:

index.mjs:69 {"step":"set","min":4,"minPos":0} index.mjs:89 {"step":"compare","originalMin":4,"originalMinPos":0,"comparePos":1,"compareTo":13}

By looking at the lines numbers from index.mjs, we can see that line 3 is output inside of the handleOk() handler, and line 4 is output from inside the handleCompare() handler. That is not ideal, as we could use the output for that second step inside of handleOk() to update the status_label. This also shows that we could use a console.log() to print the status_label each time we update this in the code. So, let’s make some changes to index.mjs to reflect these observations.

index.mjs
import { Node } from "./node.mjs";
import { SelectSorter } from "./select_sort.mjs";

if (document.readyState === "loading") {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

function removeChildren(elem) {
  while (elem.childNodes.length > 0) {
    elem.removeChild(elem.childNodes[0]);
  }
}

function createHandlers() {
  const status_label = document.getElementById("status_label");
  const min_label = document.getElementById("min_label");
  const minPos_label = document.getElementById("minPos_label");
  const comparisons_label = document.getElementById("comparisons_label");
  const swaps_label = document.getElementById("swaps_label");
  let comparisons_count = 0;
  let swaps_count = 0;
  const svg_area = document.getElementById("svg_area");
  let ss; // select sorter object
  let sort_steps = [];
  let node_array = [];
  let step_count = 0;

  function getNodeById(id) {
    for (let i = 0; i < node_array.length; i++) {
      const nd = node_array[i];
      if (Number(nd.g.id) === id) {
        return nd;
      }
    }
  }

  function resetIds() {
    for (let i = 0; i < node_array.length; i++) {
      node_array[i].g.setAttribute("id", i);
    }
  }

  return {

    handleOk(compare_button, swap_button) {
      const numbox = document.getElementById("numbox");
      removeChildren(svg_area);
      //let nums = numbox.value.trim().split(",");
      ss = new SelectSorter(numbox.value);
      step_count = 0;
      sort_steps = ss.getSortSteps();
      const original_array = ss.getOriginalArray();
      node_array = [];
      let xStart = 30;
      let yStart = 60;
      for (let i = 0; i < original_array.length; i++) {
        const num = original_array[i];
        const nd = new Node(xStart + i*(50), yStart, 30, 25, num, i);
        node_array.push(nd); // this is from the original unsorted array
        nd.draw(svg_area);
      }
      //status_label.textContent = "Getting ready to find the min value";
      comparisons_label.textContent = comparisons_count;
      swaps_label.textContent = swaps_count;
      console.log(node_array);
      const step = sort_steps[step_count];
      console.log(JSON.stringify(step));
      min_label.textContent = step.min;
      minPos_label.textContent = step.minPos;
      step_count++;  // go to next step
      const next_step = sort_steps[step_count];
      let msg = `Comparing ${next_step.originalMin} with ${next_step.compareTo}`;
      status_label.textContent = msg;
      console.log('Status:', status_label.textContent);
      compare_button.disabled = false;
      swap_button.disabled = true;
    },

    handleCompare(compare_button, swap_button) {
      if (!ss) { return; }
/* rest of code is unchanged */

The changed lines are 64 and 73-76. Note that I am not showing the entire file, as we will be making a lot of changes to index.mjs. So, you should not copy index.mjs to update your code at this point.

Line 64 comments out the update to status_label. Line 73 gets next_step from the sort steps after step_count has been incremented. Line 74 stores the updated text for status_label and stores this in msg. Line 75 updates status_label with msg. Line 76 prints the status_label contents to the console. The following shows the console output after hitting the Compare button the first two times:

console output for "4,13,1,6,22" after first two Compare clicks
index.mjs:144 init called
index.mjs:67 (5) [Node, Node, Node, Node, Node]
index.mjs:69 {"step":"set","min":4,"minPos":0}
index.mjs:76 Status: Comparing 4 with 13
index.mjs:93 {"step":"compare","originalMin":4,"originalMinPos":0,"comparePos":1,"compareTo":13}
index.mjs:93 {"step":"compare","originalMin":4,"originalMinPos":0,"comparePos":2,"compareTo":1,"minChanged":true,"min":1,"minPos":2}

Lines 2-4 are output inside the handleOk() handler. Line 4 shows that the status_label will show "Compare 4 with 13". This is before the first Compare click (as this output is from inside handleOk()).

Line 5 shows the console output when we first click on Compare. The important part of this message is actually that the minChanged property is missing. That means that the min value did not change. What we really want to show for the status_label is that min is unchanged and then what we will be comparing for the next step. Line 6 shows what we will be comparing for the next step. So, let’s modify index.mjs by making some changes in the handleCompare() handler.

index.mjs showing only the handleCompare() handler
    handleCompare(compare_button, swap_button) {
      if (!ss) { return; }
      comparisons_count++;
      comparisons_label.textContent = comparisons_count;
      const step = sort_steps[step_count];
      const next_step = sort_steps[step_count + 1];
      //let msg = `comparing ${step.originalMin} with ${step.compareTo}`
      if (next_step.step === "swap") {
        console.log(JSON.stringify(step));
        compare_button.disabled = true;
        swap_button.disabled = false;
      } else {
        console.log(JSON.stringify(step));
      }
      let msg = "";
      if (step.minChanged === true) {
        msg += "min and minPos are changed.";
        min_label.textContent = step.min;
        minPos_label.textContent = step.minPos;
      } else {
        msg += "min is unchanged.";
      }
      msg += `  Comparing ${next_step.originalMin} with ${next_step.compareTo}`;
      status_label.textContent = msg;
      console.log('Status:', status_label.textContent);
      step_count++;
    },

This time we are only displaying the handleCompare() handler in index.mjs. So, do not copy and paste this to update your index.mjs code. The lines changed are 87, 95, 97, 101 and 103-105. Line 87 comments out the original msg variable. Line 95 is the new declaration of the msg variable. That is the variable that will be used to update the status_label. Line 97 has been changed so that"min and minPos are changed." is appended to min. Line 101 does a similar thing to msg but for the case where min and minPos are unchanged. Line 103 adds what will be compared next to msg. Line 104 updates status_label with msg. Line 105 prints the updated status_label to the console.

The following console output is obtained by running the application with those changes and clicking Compare until the Swap button is enabled.

console output for "4,13,1,6,22" clicking Compare until Swap is enabled
index.mjs:147 init called
index.mjs:67 (5) [Node, Node, Node, Node, Node]
index.mjs:69 {"step":"set","min":4,"minPos":0}
index.mjs:76 Status: Comparing 4 with 13
index.mjs:93 {"step":"compare","originalMin":4,"originalMinPos":0,"comparePos":1,"compareTo":13}
index.mjs:105 Status: min is unchanged. Comparing 4 with 1
index.mjs:93 {"step":"compare","originalMin":4,"originalMinPos":0,"comparePos":2,"compareTo":1,"minChanged":true,"min":1,"minPos":2}
index.mjs:105 Status: min and minPos are changed. Comparing 1 with 6
index.mjs:93 {"step":"compare","originalMin":1,"originalMinPos":2,"comparePos":3,"compareTo":6}
index.mjs:105 Status: min is unchanged. Comparing 1 with 22
index.mjs:89 {"step":"compare","originalMin":1,"originalMinPos":2,"comparePos":4,"compareTo":22}
index.mjs:105 Status: min is unchanged. Comparing undefined with undefined

On line 6 we see how the changes affect the status_label updates. The status label will show "min is unchanged. Comparing 4 with 1". Now, this shows what we are comparing next. On line 8 the status label will show "min and minPos are changed. Comparing 1 with 6". So, this is the case where min has changed. Just as the previous update to status_label, this shows what values will be compared next. Line 10 shows the status_label right before the last Compare before a swap.

Line 12 shows what is displayed when the next step is a "swap". The msg value does not reflect that the next step is a swap. So, we need to still modify the handleCompare() handler.

index.mjs only the handleCompare() handler
    handleCompare(compare_button, swap_button) {
      if (!ss) { return; }
      comparisons_count++;
      comparisons_label.textContent = comparisons_count;
      const step = sort_steps[step_count];
      const next_step = sort_steps[step_count + 1];
      //let msg = `comparing ${step.originalMin} with ${step.compareTo}`
      if (next_step.step === "swap") {
        console.log(JSON.stringify(step));
        compare_button.disabled = true;
        swap_button.disabled = false;
      } else {
        console.log(JSON.stringify(step));
      }
      let msg = "";
      if (step.minChanged === true) {
        msg += "min and minPos are changed. ";
        min_label.textContent = step.min;
        minPos_label.textContent = step.minPos;
      } else {
        msg += "min is unchanged. ";
      }
      msg += `Comparing ${next_step.originalMin} with ${next_step.compareTo}`;
      if (next_step.step === "swap") {
        msg = `Swapping pos: ${next_step.pos1} with pos: ${next_step.pos2}`;
      }
      status_label.textContent = msg;
      console.log('Status:', status_label.textContent);
      step_count++;
    },

The new lines are 104-106. Lines 104-106 define a selection statement. Although this condition was tested on line 88, it was simpler to just have this selection statement here. If the next step is a "swap", then msg is replaced (not appended to), with the string, "Swapping pos: 0 with pos: 2".

The following console output is generated after clicking all the way through the first set of "compare" steps, and then clicking on the Swap button:

console output after clicking all the way to the first Swap
index.mjs:150 init called
index.mjs:67 (5) [Node, Node, Node, Node, Node]
index.mjs:69 {"step":"set","min":4,"minPos":0}
index.mjs:76 Status: Comparing 4 with 13
index.mjs:93 {"step":"compare","originalMin":4,"originalMinPos":0,"comparePos":1,"compareTo":13}
index.mjs:108 Status: min is unchanged. Comparing 4 with 1
index.mjs:93 {"step":"compare","originalMin":4,"originalMinPos":0,"comparePos":2,"compareTo":1,"minChanged":true,"min":1,"minPos":2}
index.mjs:108 Status: min and minPos are changed. Comparing 1 with 6
index.mjs:93 {"step":"compare","originalMin":1,"originalMinPos":2,"comparePos":3,"compareTo":6}
index.mjs:108 Status: min is unchanged. Comparing 1 with 22
index.mjs:89 {"step":"compare","originalMin":1,"originalMinPos":2,"comparePos":4,"compareTo":22}
index.mjs:108 Status: Swapping pos: 0 with pos: 2
index.mjs:117 {"step":"swap","pos1":0,"pos2":2}
index.mjs:132 {"step":"set","min":13,"minPos":1}

You can see on line 13, that the proper status_label is being used. Line 14 shows output from inside the handleSwap() handler. At this stage, the status_label that would have been displayed is "Comparing 13 to the rest of the array". While that is true, this is different from what we showed as the status_label for the first set of "compare" steps. So, let’s modify the handleSwap() handler to make this consistent.

index.mjs only the handleSwap() handler
    async handleSwap(compare_button, swap_button) {
      if (!ss) {return};
      swaps_count++;
      swaps_label.textContent = swaps_count;
      const step = sort_steps[step_count];
      console.log(JSON.stringify(step));
      const nd1 = getNodeById(step.pos1);
      const nd2 = getNodeById(step.pos2);
      if (step.pos1 !== step.pos2) {
        await nd1.swap(nd2, 700);
        const temp = node_array[step.pos1];
        node_array[step.pos1] = node_array[step.pos2];
        node_array[step.pos2] = temp;
        resetIds();
      } else {
        console.log("node already in correct place");
      }
      if (step_count < sort_steps.length - 1) {
        step_count++;  // advance to "set" step
        const set_step = sort_steps[step_count];
        console.log(JSON.stringify(set_step));
        const next_step = sort_steps[step_count + 1];
        status_label.textContent = `comparing ${set_step.min} with ${next_step.compareTo}.`;
        console.log('Status:',status_label.textContent);
        min_label.textContent = set_step.min;
        minPos_label.textContent = set_step.minPos;
        step_count++;  // advance to "compare" step
        compare_button.disabled = false;
        swap_button.disabled = true;
      } else {
        status_label.textContent = "All done";
        compare_button.disabled = true;
        swap_button.disabled = true;
      }
    }

The new lines are 133-135. Line 133 gets the next sort step and stores this as next_step. Line 134 updates the $status_label* with comparing ${set_step.min} with ${next_step.compareTo}. This will show the current min value being compared to the next array element. Line 135 just shows the status_label contents in the console. So, the console output would look like this for going through the entire sort.

console output for "4,13,1,6,22" entire sort
index.mjs:152 init called
index.mjs:67 (5) [Node, Node, Node, Node, Node]
index.mjs:69 {"step":"set","min":4,"minPos":0}
index.mjs:76 Status: Comparing 4 with 13
index.mjs:93 {"step":"compare","originalMin":4,"originalMinPos":0,"comparePos":1,"compareTo":13}
index.mjs:108 Status: min is unchanged. Comparing 4 with 1
index.mjs:93 {"step":"compare","originalMin":4,"originalMinPos":0,"comparePos":2,"compareTo":1,"minChanged":true,"min":1,"minPos":2}
index.mjs:108 Status: min and minPos are changed. Comparing 1 with 6
index.mjs:93 {"step":"compare","originalMin":1,"originalMinPos":2,"comparePos":3,"compareTo":6}
index.mjs:108 Status: min is unchanged. Comparing 1 with 22
index.mjs:89 {"step":"compare","originalMin":1,"originalMinPos":2,"comparePos":4,"compareTo":22}
index.mjs:108 Status: Swapping pos: 0 with pos: 2
index.mjs:117 {"step":"swap","pos1":0,"pos2":2}
index.mjs:132 {"step":"set","min":13,"minPos":1}
index.mjs:135 Status: comparing 13 with 4.
index.mjs:93 {"step":"compare","originalMin":13,"originalMinPos":1,"comparePos":2,"compareTo":4,"minChanged":true,"min":4,"minPos":2}
index.mjs:108 Status: min and minPos are changed. Comparing 4 with 6
index.mjs:93 {"step":"compare","originalMin":4,"originalMinPos":2,"comparePos":3,"compareTo":6}
index.mjs:108 Status: min is unchanged. Comparing 4 with 22
index.mjs:89 {"step":"compare","originalMin":4,"originalMinPos":2,"comparePos":4,"compareTo":22}
index.mjs:108 Status: Swapping pos: 1 with pos: 2
index.mjs:117 {"step":"swap","pos1":1,"pos2":2}
index.mjs:132 {"step":"set","min":13,"minPos":2}
index.mjs:135 Status: comparing 13 with 6.
index.mjs:93 {"step":"compare","originalMin":13,"originalMinPos":2,"comparePos":3,"compareTo":6,"minChanged":true,"min":6,"minPos":3}
index.mjs:108 Status: min and minPos are changed. Comparing 6 with 22
index.mjs:89 {"step":"compare","originalMin":6,"originalMinPos":3,"comparePos":4,"compareTo":22}
index.mjs:108 Status: Swapping pos: 2 with pos: 3
index.mjs:117 {"step":"swap","pos1":2,"pos2":3}
index.mjs:132 {"step":"set","min":13,"minPos":3}
index.mjs:135 Status: comparing 13 with 22.
index.mjs:89 {"step":"compare","originalMin":13,"originalMinPos":3,"comparePos":4,"compareTo":22}
index.mjs:108 Status: Swapping pos: 3 with pos: 3
index.mjs:117 {"step":"swap","pos1":3,"pos2":3}
index.mjs:127 node already in correct place

The lines showing the status_label are highlighted. This is a lot of lines, and is not easy to follow. This output was mainly used for debugging purposes. That is, they are used to help get the actual labels for the application correct. But, they are too much information to go through line by line.

Demonstrating the application using an animated gif

The following animated gif goes through a demonstration of the program as it stands at this point. It is a long animation, as I use the mouse to try to point out important changes as the program runs.

swap sort finished

Click on Reload gif to replay animation.

Here is the complete version of index.mjs up until this point.

index.mjs
import { Node } from "./node.mjs";
import { SelectSorter } from "./select_sort.mjs";

if (document.readyState === "loading") {
  document.addEventListener('DOMContentLoaded', init);
} else {
  init();
}

function removeChildren(elem) {
  while (elem.childNodes.length > 0) {
    elem.removeChild(elem.childNodes[0]);
  }
}

function createHandlers() {
  const status_label = document.getElementById("status_label");
  const min_label = document.getElementById("min_label");
  const minPos_label = document.getElementById("minPos_label");
  const comparisons_label = document.getElementById("comparisons_label");
  const swaps_label = document.getElementById("swaps_label");
  let comparisons_count = 0;
  let swaps_count = 0;
  const svg_area = document.getElementById("svg_area");
  let ss; // select sorter object
  let sort_steps = [];
  let node_array = [];
  let step_count = 0;

  function getNodeById(id) {
    for (let i = 0; i < node_array.length; i++) {
      const nd = node_array[i];
      if (Number(nd.g.id) === id) {
        return nd;
      }
    }
  }

  function resetIds() {
    for (let i = 0; i < node_array.length; i++) {
      node_array[i].g.setAttribute("id", i);
    }
  }

  return {

    handleOk(compare_button, swap_button) {
      const numbox = document.getElementById("numbox");
      removeChildren(svg_area);
      //let nums = numbox.value.trim().split(",");
      ss = new SelectSorter(numbox.value);
      step_count = 0;
      sort_steps = ss.getSortSteps();
      const original_array = ss.getOriginalArray();
      node_array = [];
      let xStart = 30;
      let yStart = 60;
      for (let i = 0; i < original_array.length; i++) {
        const num = original_array[i];
        const nd = new Node(xStart + i*(50), yStart, 30, 25, num, i);
        node_array.push(nd); // this is from the original unsorted array
        nd.draw(svg_area);
      }
      //status_label.textContent = "Getting ready to find the min value";
      comparisons_label.textContent = comparisons_count;
      swaps_label.textContent = swaps_count;
      console.log(node_array);
      const step = sort_steps[step_count];
      console.log(JSON.stringify(step));
      min_label.textContent = step.min;
      minPos_label.textContent = step.minPos;
      step_count++;  // go to next step
      const next_step = sort_steps[step_count];
      let msg = `Comparing ${next_step.originalMin} with ${next_step.compareTo}`;
      status_label.textContent = msg;
      console.log('Status:', status_label.textContent);
      compare_button.disabled = false;
      swap_button.disabled = true;
    },

    handleCompare(compare_button, swap_button) {
      if (!ss) { return; }
      comparisons_count++;
      comparisons_label.textContent = comparisons_count;
      const step = sort_steps[step_count];
      const next_step = sort_steps[step_count + 1];
      //let msg = `comparing ${step.originalMin} with ${step.compareTo}`
      if (next_step.step === "swap") {
        console.log(JSON.stringify(step));
        compare_button.disabled = true;
        swap_button.disabled = false;
      } else {
        console.log(JSON.stringify(step));
      }
      let msg = "";
      if (step.minChanged === true) {
        msg += "min and minPos are changed. ";
        min_label.textContent = step.min;
        minPos_label.textContent = step.minPos;
      } else {
        msg += "min is unchanged. ";
      }
      msg += `Comparing ${next_step.originalMin} with ${next_step.compareTo}`;
      if (next_step.step === "swap") {
        msg = `Swapping pos: ${next_step.pos1} with pos: ${next_step.pos2}`;
      }
      status_label.textContent = msg;
      console.log('Status:', status_label.textContent);
      step_count++;
    },

    async handleSwap(compare_button, swap_button) {
      if (!ss) {return};
      swaps_count++;
      swaps_label.textContent = swaps_count;
      const step = sort_steps[step_count];
      console.log(JSON.stringify(step));
      const nd1 = getNodeById(step.pos1);
      const nd2 = getNodeById(step.pos2);
      if (step.pos1 !== step.pos2) {
        await nd1.swap(nd2, 700);
        const temp = node_array[step.pos1];
        node_array[step.pos1] = node_array[step.pos2];
        node_array[step.pos2] = temp;
        resetIds();
      } else {
        console.log("node already in correct place");
      }
      if (step_count < sort_steps.length - 1) {
        step_count++;  // advance to "set" step
        const set_step = sort_steps[step_count];
        console.log(JSON.stringify(set_step));
        const next_step = sort_steps[step_count + 1];
        status_label.textContent = `comparing ${set_step.min} with ${next_step.compareTo}.`;
        console.log('Status:',status_label.textContent);
        min_label.textContent = set_step.min;
        minPos_label.textContent = set_step.minPos;
        step_count++;  // advance to "compare" step
        compare_button.disabled = false;
        swap_button.disabled = true;
      } else {
        status_label.textContent = "All done";
        compare_button.disabled = true;
        swap_button.disabled = true;
      }
    }

  }
}

function init() {
  console.log('init called');
  const handlers = createHandlers();
  const ok_button = document.getElementById("ok_button");
  const compare_button = document.getElementById("compare_button");
  const swap_button = document.getElementById("swap_button");
  ok_button.addEventListener('click', () => {
    handlers.handleOk(compare_button, swap_button);
  });
  compare_button.addEventListener('click', () => {
    handlers.handleCompare(compare_button, swap_button);
  });
  swap_button.addEventListener('click', () => {
    handlers.handleSwap(compare_button, swap_button);
  });
}

This completes this lesson. The final touches for the application will be added in the next lesson Creating User Interactive Applications part 3.

Summary

  • Modules - We covered the creation and testing of a module called select_sort.mjs. This module contained the code for performing the selection sort and creating the steps needed to animate the sort in the main program. We went over how to start on the creation and testing of a new module using both CodeSandbox and Vite.

  • Importance of Modules - The use of modules is part of writing more complex programs. Here are some of the key features of using modules.

    1. Using a module can simplify the writing of the main JavaScript code. This is because the details for that module can be kept hidden from the main code. This also reduces the number of lines that would have gone into the main code making it easier to understand the main code and easier to debug the main code.

    2. Using a module can make it easier to reuse code. If you have instructions that could be used in other programs you write, putting those lines into a module makes it easier to reuse that code. This is much better than copying and pasting the code you intend to reuse into other programs. If you discover something needs to be added or modified in the module, you only have to fix this in one place. If you had copied and pasted the code into multiple programs, you would have to go to each program and apply the fix.

  • Algorithms - We developed an algorithm for the selection sort. In addition, we discussed how writing algorithms can seem difficult at first, because as humans we tend to think heuristically, rather than algorithmically.

  • factory functions - We continued to use and modify the factory function created in the previous lesson. The use of factory functions to set up handler functions that are called with some user-interactive event, is a good pattern to use for developing more complex applications.

  • helper functions - We included some helper functions inside our factory function, createHandlers(). These helper functions could directly access variables that are private to our factory function. In addition, those helper functions are private to the factory function, since they are only needed by the handler functions that the factory function returns.

  • using two arrays to demonstrate the sort - This may seem very specific to the particular program we are working on, but the concept is actually applicable in a general sense. We created an array of the numbers, num_array, that is the actual array that we sort using the selection sort algorithm. We also created an array of Node objects, node_array that is used to represent the SVG objects used to animate the sort. This was done for several reasons. The first reason is that the algorithm that we wanted to use to manipulate the data is readily applied to an array of numbers. This algorithm would be harder to apply to an array of type Node. To see why, think of how many comparisons we make between the array elements. If we wanted to compare two Node objects (instead of two numbers), we would need to extract the number that a Node represents. If we call this Node object, nd, this would mean taking Number(nd.g.childNodes[1].textContent) just to get the number that node represents. This is detailed information that we don’t want to have to worry about when we are writing the main JavaScript code. This would also make the actual code to implement the selection sort algorithm a lot messier to look at and debug. The second reason, is that having an array of type Node is needed to perform the graphics and animation of those graphics. So, anytime you have some data that needs to have a fairly involved algorithm on, and you also want to animate that manipulation with graphics, it is better to have two separate arrays for that purpose. One array is to apply the algorithm on and to generate the animating steps. The other array is to create the graphics representation that can be animated.

  • incremental development - Although the total amount of code we have for our project is getting long, we are using modules to keep the amount of code to look at to debug relatively short. Even so, the code is developed incrementally so that it is easier to catch mistakes we might make. Using incremental development is always important, but it is especially important when using modules. This because you need to go back and forth between the main JavaScript code and the module code to make sure you are doing things correctly. This is much easier to do if you write only a few lines at a time and test.

  • adding output statements to debug/modify code - In the last part of this lessons, we put in some additional console.log() statements. This helped make it clear how to change the code so that the status_label was updated in a better fashion. When you are solving programming problems and you are having difficulty figuring out the logic of the instructions, it is often useful to add extra print statements so you can really see what is happening to the important variables. Although most programming languages have some sort of debugger for this purpose, not all programming languages have a good debugger. But, practically all programming languages allow you to add output statements that print the values of the important variables. So, developing the skill to debug/modify programs using printed output is a very practical tool to have.