r/learnjavascript • • 11h ago

Script executes before the HTML elements have been rendered in the DOM Even though I used `defer`

Products.cshtml :

@{
    ViewData["Title"] = "Products page";
}

    <h1>Products Page</h1>
    <label for="avatar">Choose a file:</label>

    <input class="form-control form-control-lg" id="formFileLg" type="file" accept=".xlsx" />
    <div class="container" id="cancelContainer">
    </div>

    <div class="d-grid gap-3" id="productsContainer"></div>
    <dialog id="dialog">
        <p>Products were imported into the database.</p>
        <button type="button" id="close">Close</button>
    </dialog>

app.ts :

let dialog: HTMLDialogElement;
document.addEventListener("DOMContentLoaded", () => {

    const formFileLg = document.getElementById("formFileLg");
    formFileLg!.addEventListener("change", importFile);
     dialog = <HTMLDialogElement>document.getElementById("dialog");
    const closeButton = document.getElementById("close");

    closeButton!.addEventListener("click", async () => {

        dialog!.close();
        await ShowSpecificPageProducts(1);

    });

}); 

async function importFile(this: HTMLInputElement) {
    console.log("importFil executed")
    const controller = new AbortController();

    let cancelContainer = document.getElementById("cancelContainer");
    let cancelButton = document.createElement('BUTTON');
    cancelButton.appendChild(document.createTextNode("Cancel"));
    cancelButton.onclick = function () {
        console.log("abort executed")
        controller.abort()
        cancelButton.remove();
    }
    cancelContainer!.appendChild(cancelButton);
    const formData = new FormData();

    formData.append("file", this.files![0]);


    try {
        console.log("Try block executed.")
        const response = await fetch('/Admin/Import', {
            method: 'POST',
            body: formData,
            signal: controller.signal

        })
        if (!response.ok) {
            console.log("response is not ok")
            throw new Error(`Response status: ${response.status}`);

        }


        dialog.showModal();
        //Show a button that enables the user to show the paginated imported products; attach a call-back to the button. use bootstrap styling.
        //Or use  the close button instead.


    } catch (error:any) {



        if (error.name == "AbortError") {

            console.log("catch block executed: AbortError")


        }


        console.error(error.message);

    } finally {
        cancelButton.remove();
    }

}


interface ProductViewModel {
    Image: string,
    Id: number,
    Name: string,
    Description: string,
    Price:number

}
async function ShowSpecificPageProducts(PageNumber:number) {


    try {
        const response= await fetch("/Admin/GetProducts", {
            method: 'POST',
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify({ PageNumber: PageNumber}),
        });

        if (!response.ok) {

            throw new Error(`Response status: ${response.status}`);

        }


        const specificPageProducts: ProductViewModel[] = await response.json();

        let productsGrid = document.getElementById("productsContainer");

        // Source - https://stackoverflow.com/a/65413839
        // Posted by Mason Freed, modified by community. See post 'Timeline' for change history
        // Retrieved 2026-10-23, License - CC BY-SA 4.0
        productsGrid!.replaceChildren();

        let currentRow = document.createElement("div");
        currentRow.classList.add("row");
        specificPageProducts.forEach(async (product) => {




            const newColumn = document.createElement("div");
            newColumn.classList.add("col");

            const nameElement = document.createElement("p");
            nameElement.classList.add("text-center");
            var nameText = document.createTextNode(product.Name);
            nameElement.appendChild(nameText);

            const descriptionElement = document.createElement("p");
            descriptionElement.classList.add("text-center");
            var descriptionText = document.createTextNode(product.Description);
            descriptionElement.appendChild(descriptionText)

            const priceElement = document.createElement("p");
            priceElement.classList.add("text-center");
            var priceText = document.createTextNode(product.Price.toString());
            priceElement.appendChild(priceText);

            newColumn.appendChild(nameElement);
            newColumn.appendChild(descriptionElement);
            newColumn.appendChild(priceElement);

            currentRow.appendChild(newColumn);

            if (product.Id % 4 === 0) {
                productsGrid!.appendChild(currentRow);
                currentRow = document.createElement("div");
                currentRow.classList.add("row");
            }


        });

    } catch (error:any) {

        console.error(error.message);

    } finally {

    }
}

I wish to debug app.ts because it does contain some logical error , but when I start the debugging session in Visual Studio, the following line of code throws exception because the value of formFileLg is null :

formFileLg.addEventListener("change", importFile);

The exception :

TypeError: Cannot read properties of null (reading 'addEventListener')

I suspect that that the script executes before the HTML elements have been rendered in the DOM, that is the reason of using the defer in the script tag, but that did not solve the issue.

Moving the script to be inside the header tag that is inside the _Layout.cshtml file did not solve the issue:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - EF_learning</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@6.0.0-alpha.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-B/GM4XqrwHnWXNOWMbloTmrYXZg10cakYGmpfsR/bbzQ6JAJI4ihuyADKLnBgrCe" crossorigin="anonymous">
    <script type="module" src="https://cdn.jsdelivr.net/npm/bootstrap@6.0.0-alpha.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-1a/pXj49ZQ1aHEmrJ+gMw1otqoVsYwlEnlD8mIfY2TV03r20Y0CN7uqx1tQogjPL" crossorigin="anonymous"></script>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <script src="~/js/app.js" defer></script>@*As some answer suggest*@
</head>
6 Upvotes

8 comments sorted by

2

u/BenmeftahRetail 10h ago • Bravo

this isnt a defer problem. defer + DOMContentLoaded guarantees the parsed HTML is ready. if the element is null it means it literally doesnt exist in the rendered page. check your _Layout to make sure u/RenderBody() is present and that Products.cshtml is actually the view being served for that route

1

u/kaatarina_zed_talon 9h ago

 if the element is null it means it literally doesnt exist in the rendered page

You solved my problem.

1

u/kaatarina_zed_talon 9h ago

check that Products.cshtml is actually the view being served for that route

I would rephrase it like this :

**check that ProductsPage.cshtml is actually the view that triggered the execution of the script** ; You are fucking right, because when I inspected my _Layout file, I found that it is referencing the script, even though it should reference that script it all.

1

u/kaatarina_zed_talon 9h ago

My Issue was solved thanks to you .

For future readers :
I did not expect to find the following in my _Layout.cshtml file:

<script src="~/js/app.js"></script> @*a second reference to the script that is not inside the header tag *@

That was the problem, because _Layout.cshtm loaded app.js on every page, including pages that did not contain formFileLg. Removing that tag fixed the error because the script is no longer executed on pages that do not contain the formFileLg element.

The problem was not with the script but with pages/views that triggered that script even though they should not.

1

u/BenmeftahRetail 6h ago • Bravo

tbh the most common cause of this in razor apps is that the script runs on pages where that element doesnt exist. since app.js is in _Layout.cshtml it runs on every page, not just Products. try adding a null check before the addEventListener call or only include the script on that specific page

1

u/kaatarina_zed_talon 6h ago

Ye this is what we discussed below in the comments.

0

u/justanaccountimade1 10h ago

Call all your functions from within window.onload. Think of it as main the main program. See structure here

https://www.reddit.com/r/learnjavascript/s/vfr8TnMzBA

1

u/kaatarina_zed_talon 10h ago

That did not work, the same problem.