TypeScript Document Object Model (DOM)



Headlines
DOM

JavaScript has a permanent access to the window object, which itself points to the DOM: window.document. At loading time, two key events occur: the fact that the DOM (i.e., the HTML “tree of content”) is ready for processing and the fact that the browser window is itself ready (i.e., all resources as images, sounds, videos… are ready for processing). Typically, any img DOM element leads to a HTMLImageElement object (Image interface in JavaScript). Accessing this DOM element strictly requires that the DOM has finished loading. However, this image cannot be processed (e.g., grayed) until the browser window has not yet finished loading all resources including this image.

Example Theo_is_crying.ts.zip 

const Sounds: Array<string> = new Array; // Available sounds...
createjs.Sound.on('fileload', (event: { id: string }) => { // This is fired for each sound that is registered...
    Sounds.push(event.id);
});
// No need to wait for either DOM or Window loaded:
createjs.Sound.registerSound("./sounds/Baby_crying.mp3", "Baby_crying_sound_ID");
// createjs.Sound.registerSound("./sounds/Baby_laughing.mp3", "Baby_laughing_sound_ID");

let my_image: HTMLImageElement | null = null;

window.document.onreadystatechange = function () { // Called *TWO TIMES*: when 'interactive' and later on... when 'complete'
    if (window.document.readyState === 'interactive') {
        window.console.log("DOM just loaded...");
        my_image = window.document.getElementById("my_image") as HTMLImageElement;
        my_image.addEventListener('mouseover', function () {
            if (Sounds.indexOf("Baby_crying_sound_ID") !== -1) {
                const sound = createjs.Sound.play("Baby_crying_sound_ID");
                sound.on('complete', () => window.console.log("Baby_crying_sound_ID" + " just completed..."));
            }
        });
    } else {
        if (window.document.readyState === 'complete') {
            window.console.log("Window just loaded...");
            if (my_image === null) { // We missed 'interactive'...
                my_image = window.document.getElementById("my_image") as HTMLImageElement;
                my_image.addEventListener('mouseover', function () {
                    if (Sounds.indexOf("Baby_crying_sound_ID") !== -1) {
                        const sound = createjs.Sound.play("Baby_crying_sound_ID");
                        sound.on('complete', () => window.console.log("Baby_crying_sound_ID" + " just completed..."));
                    }
                });
            }
            let image = new Image();
            image.onload = function () {
                window.console.log("Image ready for processing? " + image.width, 'x', image.height);
            };
            image.src = my_image.src; // Image load starts...
        }
    }
};
Synchronization with DOM and window

DOM

window.addEventListener('DOMContentLoaded', (event: Event) => {
    window.console.assert(window.document.readyState === 'interactive');
    window.console.log("DOM just loaded...");
    // Main_first_part(); // Call 'Main_first_part' program: access DOM resources...
});

window

window.addEventListener('load', (event: Event) => {
    window.console.assert(window.document.readyState === 'complete');
    window.console.log("Window just loaded...");
    // Main_second_part(); // Call 'Main_second_part' program: access *ALL* resources...
});
Xor:
window.onload = (event: Event) => {
    window.console.assert(window.document.readyState === 'complete');
    window.console.log("Window just loaded...");
    // Main_second_part(); // Call 'Main_second_part' program: access *ALL* resources...
};
Reading (querying) the DOM (see also here…)

The complexity of the DOM as a tree of data items requires powerful querying facilities.

Example Shopping.ts.zip 

<body oncontextmenu="return false;">
    <div id="Shopping"></div> <!-- The '<div>' element is a block-level element: -->
    <div class="info">
        Shopping...
    </div>
    <p class="info" id="Query">Type '20023966'></p>
</body>
const div = window.document.querySelector('div'); // Get the first '<div>' element...
window.alert(div!.constructor); // 'function HTMLDivElement()...'
const div_info = window.document.querySelector('div.info'); // Get the first '<div>' element with class 'info'...
window.alert(div_info!.constructor); // 'function HTMLDivElement()...'
Writing the DOM

The DOM embodies a tree of data items transformable by JavaScript in terms of style (CSS); Other modifications apply on content itself. Beyond, JavaScript may add or remove content items: the switch from static Web to dynamical Web.

const canvas = window.document.createElement('CANVAS') as HTMLCanvasElement;
canvas.width = 600;
canvas.height = 400;
const image_data = canvas.getContext('2d')!.getImageData(0, 0, 600, 400);
let pixel_number = 600 * 400;
while (--pixel_number >= 0)
    image_data.data[pixel_number] = buffer[pixel_number]; // Create image content from 'buffer'...
canvas.getContext('2d')!.putImageData(image_data, 0, 0);
window.document.getElementById("Somewhere_in_the_DOM")!.appendChild(canvas);
window.document.getElementById("Somewhere_in_the_DOM")!.insertBefore(something_to_insert, window.document.getElementById("Somewhere_in_the_DOM")!.childNodes[0]);
Dealing with CSS

Example Shopping.ts.zip 

const shopping = window.document.getElementById('Shopping') as HTMLDivElement;
window.console.assert(shopping !== null, "'shopping === null', why?");

shopping.style.display = 'grid';
// '"suppressImplicitAnyIndexErrors": true,' required:
shopping.style['grid-template-columns'] = "repeat(" + Shopping._Number_of_columns + ", 1fr)";
shopping.style['grid-template-rows'] = "fit-content(" + (100 / Shopping._Number_of_rows) + "%)";
shopping.style['align-items'] = 'center'; /* Value of 'align-self' for children */
shopping.style['justify-items'] = 'flex-start'; /* Value of 'justify-self' for children */

for (let i = 0; i < Shopping._Number_of_columns * Shopping._Number_of_rows; i++) {
    let image = new Image;
    image.style.height = (100 / (Shopping._Number_of_rows)) + "vh";
    image.setAttribute("id", "Shopping_image" + i);
    image.setAttribute("index", i.toString()); // Number is transformed into string!
    image.style.opacity = "0.5";
    image.setAttribute("time-out", "0");
    image.style.width = (100 / Shopping._Number_of_columns) + "vw";
    image.onload = () => {
        shopping.appendChild(image); // Images are stored in a random way...
    };
    image.src = './img/Franck.jpg';
}

Check content

Filtering on a CSS style value is impossible in a declarative way using querySelector or querySelectorAll; it may be done in an algorithmic way. Beyond, while jQuery has support for, JavaScript puts forward the introduction of a class, which matches the targeted CSS value.

// Caution: must wait for all 'shopping.appendChild(image);' finished:
window.console.assert(shopping.childNodes.length === Shopping._Number_of_columns * Shopping._Number_of_rows);
// Filtering by CSS style value is impossible...
// Get all 'HTMLImageElement' objects that have a 'style' attribute:
const images = shopping.querySelectorAll('img[style]');
window.console.assert(images.length === Shopping._Number_of_columns * Shopping._Number_of_rows);
Form

Rule(s)

Example Form.js.zip 

<form id="my_form" name="my_form" method="post" action="https://reqres.in/api/users">
    <p><label>Name: <input name="name" required></label></p>
    <p><label>Job: <input name="job" required></label></p>
    <p><label>Phone: <input type=tel name="phone" pattern="[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}"></label> <small>(format: 12-34-45-67)</small></p>
    <p><label>E-mail: <input type=email name="e_mail"></label></p>
    <fieldset>
        <legend>Humor</legend>
        <p><label><input type=radio name=good value="good"> Good</label></p>
        <p><label><input type=radio name=between value="between"> Not so good, not too bad</label></p>
        <p><label><input type=radio name=bad value="bad"> Bad</label></p>
    </fieldset>
    <p><label>Available from: <input type="date" list="my_availabilities" name="availability"></label></p>
    <p><label>Description: <textarea name="description"></textarea></label></p>
</form>
<p><button form='my_form'>Send form...</button></p>

Example (FormData in XMLHttpRequest) Form.js.zip 

class ReqRes {
    static  URL() { return 'https://reqres.in'; }
    static  Path() { return '/api/users'; }
    …
    static Run2(name = "FranckBarbier", job = "Trainer") {
        const form_data = new FormData(); // Empty...
        form_data.append("name", name);
        form_data.append("job", job);
        const request = new XMLHttpRequest();
        request.onreadystatechange = function () {
            if (request.readyState === XMLHttpRequest.DONE) {
                window.console.assert(request.status === 201); // '201' -> Created
                if (request.responseType === "") // An empty 'responseType' string is treated the same as "text", the default type...
                    window.alert("'ReqRes' ('XMLHttpRequest') RUN2: " + request.responseText);
                if (request.responseType === "json") 
                    window.alert(Object.getOwnPropertyNames(JSON.parse(request)).join(" - "));
            }
        };
        request.open('POST', ReqRes.URL() + ReqRes.Path());
        request.setRequestHeader('accept', 'application/json; charset=UTF-8');
        request.setRequestHeader('content-type', 'application/x-www-form-urlencoded'); // Mandatory to avoid CORS...
        request.send(form_data);
    }
    …

Example (FormData in fetch) Form.js.zip 

class ReqRes {
    static  URL() { return 'https://reqres.in'; }
    static  Path() { return '/api/users'; }
    …
    static Run3(name = "FranckBarbier", job = "Trainer") {
        const my_form = window.document.getElementById('my_form'); // Not empty...
        const form_data = new FormData(my_form); // Default content type: 'multipart/form-data'
        window.console.assert(form_data.has("name") && form_data.has("job"));
        form_data.set("name", name);
        form_data.set("job", job);
        fetch(ReqRes.URL() + ReqRes.Path(), {
            body: form_data, /*new URLSearchParams("name=" + name + "&job=" + job),*/
            headers: {
                'accept': 'application/json; charset=UTF-8', // -> response
                'content-type': /*'application/x-www-form-urlencoded; charset=UTF-8'*/'multipart/form-data; charset=UTF-8' // Mandatory to avoid CORS...
            },
            method: 'POST'
        }).then(response => { window.console.assert(response.ok && response.status === 201); // '201' -> Created
            response.json().then(result => { // window.console.assert(result.name === name); // It fails...
                window.alert("'ReqRes' ('fetch') RUN3: " + JSON.stringify(result));
            });
        });
    }
    …

Example (Form, submit event) Form.js.zip 

window.document.onreadystatechange = function () {
    if (window.document.readyState === "interactive") {
        window.document.forms['my_form'].onsubmit = async(event) => { // Subscription...
            event.preventDefault();
            // window.alert("You just press 'Send form...'");
            const body = new URLSearchParams([...new FormData(event.target).entries()]);
// <form id="my_form" name="my_form" method="post" action="https://reqres.in/api/users">
            const response = await new Response(body).text();
            window.alert(response);
        };
    }
};