I want a loader element to show while my Sudoku solver function is running and hide upon completion. However, what happens in practice is that the loader never shows itself (or shows and hides almost instantly) even while the solver function is running.
solveButton.onclick = function() { //Start timing const t0 = performance.now(); //Return error if input invalid if (textArea.value.length != 81) { return (document.getElementById("error-msg").innerHTML = "<span style='color: red'>Error: Expected puzzle to be 81 characters long.</span>"); } // Show Loader document.getElementById("container").style.display = "none"; document.getElementById("loader").style.display = "flex"; //Run Solver let solution = solveSudoku() console.log("Final boardState", solution); //Populate elements with solution let solutionString = ""; for (let idx in solution) { solutionString += solution[idx].value; } textArea.value = solutionString; Array.from(sudokuInputs).forEach((ele, idx) => { ele.value = solutionString[idx]; }); //Hide Loader document.getElementById("loader").style.display = "none"; document.getElementById("container").style.display = "flex"; //Stop timing and show performance const t1 = performance.now(); document.getElementById("error-msg").innerHTML = `<span style='color: green'>Solved in ${(t1 - t0).toFixed(3)} milliseconds!</span>`; };
The timer for showing performance seems to be working and acting synchronously while the loader isn’t, leading me to believe it is an issue with updating the DOM. While debugging, I found that the showing and hiding of the loader occur almost simultaneously once my solver function has finished running.
How do I fix it such that my loader shows and hide as intended in a synchronous fashion?
Solver function:
function solveSudoku() { let sudokuString = textArea.value; let rowsArray = [[], [], [], [], [], [], [], [], []]; let columnsArray = [[], [], [], [], [], [], [], [], []]; let gridsArray = [[], [], [], [], [], [], [], [], []]; let boardState = []; sudokuString.split("").forEach((ele, idx) => { let rowIdx = Math.floor(idx / 9); let columnIdx = idx % 9; let gridIdx = (Math.floor(idx / 3) % 3) + Math.floor(idx / 27) * 3; rowsArray[rowIdx].push(ele); columnsArray[columnIdx].push(ele); gridsArray[gridIdx].push(ele); let pointObj = { index: idx, value: ele, row: rowIdx, column: columnIdx, grid: gridIdx, }; boardState.push(pointObj); }); console.log("Initial boardState", boardState); //check if duplicate in initial boardState: for (let i in boardState) { let pos = boardState[i]; //console.log(String(pos.value)) let checkIfDuplicate = rowsArray[pos.row].filter((item) => (item == pos.value ? true : false)) .length > 1 || columnsArray[pos.column].filter((item) => item == pos.value ? true : false ).length > 1 || gridsArray[pos.grid].filter((item) => (item == pos.value ? true : false)) .length > 1; if (boardState[i].value != ".") { if (checkIfDuplicate) { return (document.getElementById("error-msg").innerHTML = "<span style='color: red'>Error: Invalid initial input.</span>"); } } //console.log(boardState[i].value) } let currentIdx = 0; let lastModifiedIdxArray = [0]; let lastModifiedValArray = [1]; let backtracked = false; while (currentIdx < 81) { let pos = boardState[currentIdx]; //let lastUsedNum = 1; if (pos.value == ".") { selectNum: for (var i = 1; i < 11; i++) { if (backtracked) { backtracked = false; } if (i < 10) { let ans = String(i); let checkIfValid = !rowsArray[pos.row].includes(ans) && !columnsArray[pos.column].includes(ans) && !gridsArray[pos.grid].includes(ans); if (checkIfValid) { pos.value = ans; rowsArray[pos.row][pos.column] = ans; columnsArray[pos.column][pos.row] = ans; gridsArray[pos.grid][(pos.column % 3) + 3 * (pos.row % 3)] = ans; lastModifiedValArray.push(i); lastModifiedIdxArray.push(currentIdx); backtracked = false; break selectNum; } } else { //Handles running out of available nums and needing to backtrack //console.log(`Backtracked from pos ${currentIdx} to pos ${lastModifiedIdxArray[lastModifiedIdxArray.length - 1]}`) backtracked = true; currentIdx = lastModifiedIdxArray[lastModifiedIdxArray.length - 1]; lastModifiedIdxArray.pop(); pos = boardState[currentIdx]; pos.value = "."; rowsArray[pos.row][pos.column] = "."; columnsArray[pos.column][pos.row] = "."; gridsArray[pos.grid][(pos.column % 3) + 3 * (pos.row % 3)] = "."; i = lastModifiedValArray[lastModifiedValArray.length - 1]; lastModifiedValArray.pop(); continue; } } } currentIdx += 1; } return boardState; }
Advertisement
Answer
Just found a solution, which is to wrap the Solver function with a setTimeout function to provide time for the DOM to update:
solveButton.onclick = function (){ //Return error if input invalid if (textArea.value.length != 81) { return (document.getElementById("error-msg").innerHTML = "<span style='color: red'>Error: Expected puzzle to be 81 characters long.</span>"); } //Show Loader document.getElementById("container").style.display = "none"; document.getElementById("loader").style.display = "flex"; /////////////SET TIMER////////////// setTimeout(function() { //Start timing const t0 = performance.now(); //Run Solver let solution = solveSudoku() //Error Handling. Uncomment loader once loader issue fixed if (!solution) { //document.getElementById("loader").style.display = "none"; //document.getElementById("container").style.display = "flex" return (document.getElementById("error-msg").innerHTML = "<span style='color: red'>Error: Invalid initial input.</span>"); } if (solution == "no solution") { //document.getElementById("loader").style.display = "none"; //document.getElementById("container").style.display = "flex" return (document.getElementById("error-msg").innerHTML = "<span style='color: red'>Error: No solution found.</span>"); } console.log("Final boardState", solution); //Populate elements with solution let solutionString = ""; for (let idx in solution) { solutionString += solution[idx].value; } textArea.value = solutionString; Array.from(sudokuInputs).forEach((ele, idx) => { ele.value = solutionString[idx]; }); //Hide Loader. Uncomment once loader issue fixed document.getElementById("loader").style.display = "none"; document.getElementById("container").style.display = "flex"; //Stop timing and show performance const t1 = performance.now(); document.getElementById("error-msg").innerHTML = `<span style='color: lightgreen'>Solved in ${(t1-t0 > 50)?(t1 - t0).toFixed(3):"less than 50"} milliseconds!</span>`; }, 50) };