javascripthtmljson

Adjusting position and ranking in a dynamic table


I have created a piece of code that works with a JSON file to store table data, one of the things the user can do when adding entries is set the rank of their entry. Say we have 5 entries:

Rank = 1 (A),2 (B),3 (C),4 (D),5 (E) respectively then they add an entry with Rank = 18 it should automatically become Rank = 6 (F) to follow the sequential order. Now alternatively if they now edit an existing entry (F) with Rank = 6 and edit its rank to become 1 then the new order should be:

Rank = 1 (F)
Rank = 2 (A)
Rank = 3 (B)
Rank = 4 (C)
Rank = 5 (D)
Rank = 6 (E)

But in my implementation this behavior is not observed instead the ranks don't seem to swap at all.

This is my current code that I am working on:

const autosaveDelay = 1000; // Autosave delay in milliseconds

const tableBody = document.getElementById("table-body");

// WebSocket connection
const socket = new WebSocket(`wss://${window.location.host}`);

socket.addEventListener("open", function () {
  console.log("WebSocket is connected.");
});

socket.addEventListener("message", function (event) {
  if (event.data instanceof Blob) {
    const reader = new FileReader();
    reader.onload = function () {
      const data = JSON.parse(reader.result);
      updateTable(data);
    };
    reader.readAsText(event.data);
  } else {
    const data = JSON.parse(event.data);
    updateTable(data);
  }
});

// Add new entry
document
  .getElementById("add-entry")
  .addEventListener("click", function () {
    const rank = parseInt(document.getElementById("rank").value, 10);
    const mowie = document.getElementById("mowie").value;
    const rating = document.getElementById("rating").value + "/10";
    const genre = document.getElementById("genre").value;

    if (mowie && rating && genre) {
      let nextRank = rank || tableBody.rows.length + 1;

      if (rank) {
        // Adjust ranks if a rank is provided
        const rows = Array.from(tableBody.rows);
        rows.forEach((row) => {
          const currentRank = parseInt(row.cells[1].innerText, 10);
          if (currentRank >= rank) {
            row.cells[1].innerText = currentRank + 1;
          }
        });
      }

      const row = tableBody.insertRow();
      row.insertCell(0).innerHTML =
        '<button class="delete-btn">Delete</button>';
      row.insertCell(1).innerText = nextRank; // Assign rank
      row.insertCell(2).innerText = mowie;
      row.insertCell(3).innerText = rating;
      row.insertCell(4).innerText = genre;

      updateAndSave(); // Save the new entry
      document.getElementById("rank").value = "";
      document.getElementById("mowie").value = "";
      document.getElementById("rating").value = "";
      document.getElementById("genre").value = "";
    }
  });

// Update and save function
function updateAndSave() {
  const rows = Array.from(tableBody.rows);
  
  // Create an array to hold the updated data
  const updatedData = rows.map((row, index) => ({
    rank: index + 1, // Assign sequential ranks starting from 1
    mowie: row.cells[2].innerText,
    rating: row.cells[3].innerText,
    genre: row.cells[4].innerText,
  }));

  // Update the table with new ranks
  rows.forEach((row, index) => {
    row.cells[1].innerText = updatedData[index].rank;
  });

  updateTable(updatedData);

  // Send updated data to the server
  fetch(`/save-file`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(updatedData),
  }).catch((error) => console.error("Error saving file:", error));
}

// Autosave on content change
let timeout;
tableBody.addEventListener("input", function () {
  clearTimeout(timeout);
  timeout = setTimeout(updateAndSave, autosaveDelay); // Autosave after specified delay of inactivity
});

// Load the file content when the page loads
window.onload = function () {
  fetch(`/get-file-content`)
    .then((response) => response.json())
    .then((data) => {
      updateTable(data);
    })
    .catch((error) => console.error("Error loading file:", error));
};

// Update table with new data
function updateTable(data) {
  tableBody.innerHTML = "";
  data.forEach((item) => {
    const row = tableBody.insertRow();
    row.insertCell(0).innerHTML =
      '<button class="delete-btn">Delete</button>';
    row.insertCell(1).innerText = item.rank;
    row.insertCell(2).innerText = item.mowie;
    row.insertCell(3).innerText = item.rating;
    row.insertCell(4).innerText = item.genre;
  });
}

// Handle cell editing
tableBody.addEventListener("dblclick", function (event) {
  const cell = event.target.closest("td");
  if (cell && cell.cellIndex > 0) {
    // Allow editing in all columns except Actions
    const originalText = cell.innerText;
    const input = document.createElement("input");
    input.value = originalText;
    input.style.width = "100%";
    input.style.boxSizing = "border-box";
    input.style.backgroundColor = "var(--input-background)";
    input.style.border = "1px solid var(--input-border-color)";
    input.style.color = "var(--input-text-color)";
    input.style.padding = "8px";
    input.style.borderRadius = "4px";

    cell.innerHTML = "";
    cell.appendChild(input);

    input.focus();
    input.addEventListener("blur", function () {
      const newValue = input.value;
      if (newValue !== originalText) {
        if (cell.cellIndex === 1) {
          // Rank column
          const newRank = parseInt(newValue, 10);
          if (!isNaN(newRank) && newRank > 0) {
            // Update ranks for rows with rank >= newRank
            const rows = Array.from(tableBody.rows);
            rows.forEach((row) => {
              const rankCell = row.cells[1];
              const rankValue = parseInt(rankCell.innerText, 10);
              if (rankValue >= newRank && rankCell !== cell) {
                rankCell.innerText = rankValue + 1;
              }
            });

            // Update the rank of the current cell
            cell.innerText = newRank;

            // Reorder and update ranks
            sortAndReassignRanks(); // Reorder rows and update ranks
          } else {
            cell.innerText = originalText;
          }
        } else {
          cell.innerText = newValue;
        }
        updateAndSave(); // Only update and save after changes
      } else {
        cell.innerText = originalText;
      }
    });

    input.addEventListener("keydown", function (event) {
      if (event.key === "Enter") {
        input.blur();
      }
    });
  }
});

// Handle row deletion
tableBody.addEventListener("click", function (event) {
  if (event.target.classList.contains("delete-btn")) {
    const row = event.target.closest("tr");
    row.remove();

    // Adjust ranks after deletion
    updateAndSave(); // Ranks will be sequentially updated in updateAndSave
  }
});
:root {
  --background-color: #1c1c1c;
  --text-color: #f4f4f4;
  --header-background: #e67e22;
  --header-border: #d35400;
  --container-background: #333;
  --textarea-background: #2c2c2c;
  --border-color: #e67e22;
  --status-text-color: #f1c40f;
  --saved-text-color: #e74c3c;
  --scrollbar-thumb: #e67e22;
  --scrollbar-track: #333;
  --button-background: #d35400;
  --button-text-color: #fff;
  --button-border-color: #e67e22;
  --button-hover-background: #e67e22;
  --button-hover-text-color: #fff;
  --button-delete-background: #c0392b;
  --button-delete-text-color: #fff;
  --button-delete-hover-background: #e74c3c;
  --button-delete-hover-text-color: #fff;
  --input-background: #2c2c2c;
  --input-border-color: #e67e22;
  --input-text-color: #fff;
}

body {
  font-family: "Arial", sans-serif;
  margin: 0;
  padding: 0;
  background: var(--background-color);
  color: var(--text-color);
  text-align: center;
}

header {
  background-color: var(--header-background);
  color: #fff;
  padding: 20px;
  font-size: 2.5em;
  border-bottom: 3px solid var(--header-border);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
  position: relative;
  overflow: hidden;
  z-index: 1;
}

header::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: url("https://www.example.com/halloween-pattern.png")
    no-repeat center center;
  background-size: cover;
  opacity: 0.2;
  z-index: -1;
}

.container {
  max-width: 800px;
  margin: 40px auto;
  padding: 20px;
  background: var(--container-background);
  border-radius: 12px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
  overflow: hidden;
  text-align: left;
}

table {
  width: 100%;
  border-collapse: collapse;
  margin: 20px 0;
}

th,
td {
  border: 1px solid var(--border-color);
  padding: 8px;
  text-align: center;
}

th {
  background-color: var(--header-background);
  color: #fff;
}

td input {
  width: 100%;
  box-sizing: border-box;
  background-color: var(--input-background);
  border: 1px solid var(--input-border-color);
  color: var(--input-text-color);
  padding: 8px;
  border-radius: 4px;
}

.add-entry {
  margin-bottom: 20px;
}

.add-entry input,
.add-entry button {
  margin: 5px 0;
  padding: 10px;
  border-radius: 6px;
  border: 1px solid var(--button-border-color);
  background-color: var(--input-background);
  color: var(--input-text-color);
}

.add-entry button {
  background-color: var(--button-background);
  color: var(--button-text-color);
  cursor: pointer;
  transition: background-color 0.3s ease, border-color 0.3s ease;
}

.add-entry button:hover {
  background-color: var(--button-hover-background);
  border-color: var(--button-hover-background);
}

.delete-btn {
  background-color: var(--button-delete-background);
  color: var(--button-delete-text-color);
  border: none;
  padding: 5px 10px;
  border-radius: 6px;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

.delete-btn:hover {
  background-color: var(--button-delete-hover-background);
  color: var(--button-delete-text-color);
}

::-webkit-scrollbar {
  width: 12px;
}

::-webkit-scrollbar-track {
  background: var(--scrollbar-track);
  border-radius: 10px;
}

::-webkit-scrollbar-thumb {
  background: var(--scrollbar-thumb);
  border-radius: 10px;
}

::-webkit-scrollbar-thumb:hover {
  background: #d35400;
}

body {
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
}

.actions-column {
  width: 60px;
}

.mowie-column {
  width: calc(100% - 160px);
}

th,
td {
  text-align: center;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Mowie Ranking</title>
  </head>
  <body>
    <header>Mowie Ranking</header>

    <div class="container">
      <div class="add-entry">
        <input type="number" id="rank" placeholder="Rank" min="1" />
        <input type="text" id="mowie" placeholder="Mowie Name" />
        <input
          type="number"
          id="rating"
          placeholder="Rating (1-10)"
          min="1"
          max="10"
        />
        <input type="text" id="genre" placeholder="Genre" />
        <button id="add-entry">Add Entry</button>
      </div>
      <table>
        <thead>
          <tr>
            <th class="actions-column">Actions</th>
            <th>Rank</th>
            <th class="mowie-column">Mowie</th>
            <th>Rating</th>
            <th>Genre</th>
          </tr>
        </thead>
        <tbody id="table-body">
          <!-- Table rows will be dynamically inserted here -->
        </tbody>
      </table>
    </div>
  </body>
</html>


Solution

  • There were a couple of issues in your code.

    First, you declared updateTable(updatedData) before defining updatedData.

    Second, you declared a function sortAndReassignRanks() inside the function which is supposed to update the record but this function was not written in your whole script.

    So, I just fixed these two things and now it is working.

    const autosaveDelay = 1000; // Autosave delay in milliseconds
    
    const tableBody = document.getElementById("table-body");
    
    // WebSocket connection
    const socket = new WebSocket(`wss://${window.location.host}`);
    
    socket.addEventListener("open", function () {
      console.log("WebSocket is connected.");
    });
    
    socket.addEventListener("message", function (event) {
      if (event.data instanceof Blob) {
        const reader = new FileReader();
        reader.onload = function () {
          const data = JSON.parse(reader.result);
          updateTable(data);
        };
        reader.readAsText(event.data);
      } else {
        const data = JSON.parse(event.data);
        updateTable(data);
      }
    });
    
    // Add new entry
    document
      .getElementById("add-entry")
      .addEventListener("click", function () {
        const rank = parseInt(document.getElementById("rank").value, 10);
        const mowie = document.getElementById("mowie").value;
        const rating = document.getElementById("rating").value + "/10";
        const genre = document.getElementById("genre").value;
    
        if (mowie && rating && genre) {
          let nextRank = rank || tableBody.rows.length + 1;
    
          if (rank) {
            // Adjust ranks if a rank is provided
            const rows = Array.from(tableBody.rows);
            rows.forEach((row) => {
              const currentRank = parseInt(row.cells[1].innerText, 10);
              if (currentRank >= rank) {
                row.cells[1].innerText = currentRank + 1;
              }
            });
          }
    
          const row = tableBody.insertRow();
          row.insertCell(0).innerHTML =
            '<button class="delete-btn">Delete</button>';
          row.insertCell(1).innerText = nextRank; // Assign rank
          row.insertCell(2).innerText = mowie;
          row.insertCell(3).innerText = rating;
          row.insertCell(4).innerText = genre;
    
          updateAndSave(); // Save the new entry
          document.getElementById("rank").value = "";
          document.getElementById("mowie").value = "";
          document.getElementById("rating").value = "";
          document.getElementById("genre").value = "";
        }
      });
    
    // Update and save function
    function updateAndSave() {
      const rows = Array.from(tableBody.rows);
      
      // Create an array to hold the updated data
      const updatedData = rows.map((row, index) => ({
        rank: index + 1, // Assign sequential ranks starting from 1
        mowie: row.cells[2].innerText,
        rating: row.cells[3].innerText,
        genre: row.cells[4].innerText,
      }));
    
      // Update the table with new ranks
      rows.forEach((row, index) => {
        row.cells[1].innerText = updatedData[index].rank;
      });
    
      updateTable(updatedData);
    
      // Send updated data to the server
      fetch(`/save-file`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(updatedData),
      }).catch((error) => console.error("Error saving file:", error));
    }
    
    // Autosave on content change
    let timeout;
    tableBody.addEventListener("input", function () {
      clearTimeout(timeout);
      timeout = setTimeout(updateAndSave, autosaveDelay); // Autosave after specified delay of inactivity
    });
    
    // Load the file content when the page loads
    window.onload = function () {
      fetch(`/get-file-content`)
        .then((response) => response.json())
        .then((data) => {
          updateTable(data);
        })
        .catch((error) => console.error("Error loading file:", error));
    };
    
    // Update table with new data
    function updateTable(data) {
      tableBody.innerHTML = "";
      data.forEach((item) => {
        const row = tableBody.insertRow();
        row.insertCell(0).innerHTML =
          '<button class="delete-btn">Delete</button>';
        row.insertCell(1).innerText = item.rank;
        row.insertCell(2).innerText = item.mowie;
        row.insertCell(3).innerText = item.rating;
        row.insertCell(4).innerText = item.genre;
      });
    }
    
    // Handle cell editing
    tableBody.addEventListener("dblclick", function (event) {
      const cell = event.target.closest("td");
      if (cell && cell.cellIndex > 0) {
        // Allow editing in all columns except Actions
        const originalText = cell.innerText;
        const input = document.createElement("input");
        input.value = originalText;
        input.style.width = "100%";
        input.style.boxSizing = "border-box";
        input.style.backgroundColor = "var(--input-background)";
        input.style.border = "1px solid var(--input-border-color)";
        input.style.color = "var(--input-text-color)";
        input.style.padding = "8px";
        input.style.borderRadius = "4px";
    
        cell.innerHTML = "";
        cell.appendChild(input);
    
        input.focus();
        input.addEventListener("blur", function () {
          const newValue = input.value;
          if (newValue !== originalText) {
            if (cell.cellIndex === 1) {
              // Rank column
              const newRank = parseInt(newValue, 10);
              if (!isNaN(newRank) && newRank > 0) {
                // Update ranks for rows with rank >= newRank
                const rows = Array.from(tableBody.rows);
                rows.forEach((row) => {
                  const rankCell = row.cells[1];
                  const rankValue = parseInt(rankCell.innerText, 10);
                  if (rankValue >= newRank && rankCell !== cell) {
                    rankCell.innerText = rankValue + 1;
                  }
                });
    
                // Update the rank of the current cell
                cell.innerText = newRank;
    
                // Reorder and update ranks
                sortAndReassignRanks(); // Reorder rows and update ranks
              } else {
                cell.innerText = originalText;
              }
            } else {
              cell.innerText = newValue;
            }
            updateAndSave(); // Only update and save after changes
          } else {
            cell.innerText = originalText;
          }
        });
    
        input.addEventListener("keydown", function (event) {
          if (event.key === "Enter") {
            input.blur();
          }
        });
      }
    });
    
    // Function to sort rows by rank and reassign sequential ranks
    function sortAndReassignRanks() {
      const rows = Array.from(tableBody.rows);
    
      // Sort the rows by the rank value in the second cell (index 1)
      rows.sort((a, b) => {
        const rankA = parseInt(a.cells[1].innerText, 10);
        const rankB = parseInt(b.cells[1].innerText, 10);
        return rankA - rankB;
      });
    
      // Clear the table and reinsert rows in sorted order
      tableBody.innerHTML = "";
      rows.forEach((row, index) => {
        row.cells[1].innerText = index + 1; // Reassign sequential ranks
        tableBody.appendChild(row);
      });
    
      // Call updateAndSave to persist the changes
      updateAndSave();
    }
    
    // Handle row deletion
    tableBody.addEventListener("click", function (event) {
      if (event.target.classList.contains("delete-btn")) {
        const row = event.target.closest("tr");
        row.remove();
    
        // Adjust ranks after deletion
        updateAndSave(); // Ranks will be sequentially updated in updateAndSave
      }
    });
    :root {
      --background-color: #1c1c1c;
      --text-color: #f4f4f4;
      --header-background: #e67e22;
      --header-border: #d35400;
      --container-background: #333;
      --textarea-background: #2c2c2c;
      --border-color: #e67e22;
      --status-text-color: #f1c40f;
      --saved-text-color: #e74c3c;
      --scrollbar-thumb: #e67e22;
      --scrollbar-track: #333;
      --button-background: #d35400;
      --button-text-color: #fff;
      --button-border-color: #e67e22;
      --button-hover-background: #e67e22;
      --button-hover-text-color: #fff;
      --button-delete-background: #c0392b;
      --button-delete-text-color: #fff;
      --button-delete-hover-background: #e74c3c;
      --button-delete-hover-text-color: #fff;
      --input-background: #2c2c2c;
      --input-border-color: #e67e22;
      --input-text-color: #fff;
    }
    
    body {
      font-family: "Arial", sans-serif;
      margin: 0;
      padding: 0;
      background: var(--background-color);
      color: var(--text-color);
      text-align: center;
    }
    
    header {
      background-color: var(--header-background);
      color: #fff;
      padding: 20px;
      font-size: 2.5em;
      border-bottom: 3px solid var(--header-border);
      box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
      position: relative;
      overflow: hidden;
      z-index: 1;
    }
    
    header::before {
      content: "";
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: url("https://www.example.com/halloween-pattern.png")
        no-repeat center center;
      background-size: cover;
      opacity: 0.2;
      z-index: -1;
    }
    
    .container {
      max-width: 800px;
      margin: 40px auto;
      padding: 20px;
      background: var(--container-background);
      border-radius: 12px;
      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
      overflow: hidden;
      text-align: left;
    }
    
    table {
      width: 100%;
      border-collapse: collapse;
      margin: 20px 0;
    }
    
    th,
    td {
      border: 1px solid var(--border-color);
      padding: 8px;
      text-align: center;
    }
    
    th {
      background-color: var(--header-background);
      color: #fff;
    }
    
    td input {
      width: 100%;
      box-sizing: border-box;
      background-color: var(--input-background);
      border: 1px solid var(--input-border-color);
      color: var(--input-text-color);
      padding: 8px;
      border-radius: 4px;
    }
    
    .add-entry {
      margin-bottom: 20px;
    }
    
    .add-entry input,
    .add-entry button {
      margin: 5px 0;
      padding: 10px;
      border-radius: 6px;
      border: 1px solid var(--button-border-color);
      background-color: var(--input-background);
      color: var(--input-text-color);
    }
    
    .add-entry button {
      background-color: var(--button-background);
      color: var(--button-text-color);
      cursor: pointer;
      transition: background-color 0.3s ease, border-color 0.3s ease;
    }
    
    .add-entry button:hover {
      background-color: var(--button-hover-background);
      border-color: var(--button-hover-background);
    }
    
    .delete-btn {
      background-color: var(--button-delete-background);
      color: var(--button-delete-text-color);
      border: none;
      padding: 5px 10px;
      border-radius: 6px;
      cursor: pointer;
      transition: background-color 0.3s ease;
    }
    
    .delete-btn:hover {
      background-color: var(--button-delete-hover-background);
      color: var(--button-delete-text-color);
    }
    
    ::-webkit-scrollbar {
      width: 12px;
    }
    
    ::-webkit-scrollbar-track {
      background: var(--scrollbar-track);
      border-radius: 10px;
    }
    
    ::-webkit-scrollbar-thumb {
      background: var(--scrollbar-thumb);
      border-radius: 10px;
    }
    
    ::-webkit-scrollbar-thumb:hover {
      background: #d35400;
    }
    
    body {
      scrollbar-width: thin;
      scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
    }
    
    .actions-column {
      width: 60px;
    }
    
    .mowie-column {
      width: calc(100% - 160px);
    }
    
    th,
    td {
      text-align: center;
    }
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Mowie Ranking</title>
      </head>
      <body>
        <header>Mowie Ranking</header>
    
        <div class="container">
          <div class="add-entry">
            <input type="number" id="rank" placeholder="Rank" min="1" />
            <input type="text" id="mowie" placeholder="Mowie Name" />
            <input
              type="number"
              id="rating"
              placeholder="Rating (1-10)"
              min="1"
              max="10"
            />
            <input type="text" id="genre" placeholder="Genre" />
            <button id="add-entry">Add Entry</button>
          </div>
          <table>
            <thead>
              <tr>
                <th class="actions-column">Actions</th>
                <th>Rank</th>
                <th class="mowie-column">Mowie</th>
                <th>Rating</th>
                <th>Genre</th>
              </tr>
            </thead>
            <tbody id="table-body">
              <!-- Table rows will be dynamically inserted here -->
            </tbody>
          </table>
        </div>
      </body>
    </html>