How I made a lightweight JavaScript Framework and then used it to build my Website - jasna.js

I am not often using JavaScript, and I am sure that there is already a framework which does the same thing. I didn't even expect to use it, but surprisingly it really helped to build my website.
The minified version only takes up 479 bytes. Not even half a kilobyte!

Get it on GitHub

Why I built it

The problem I tried to solve

When building a website the HTML file can get heavily nested quite fast. I wanted to split up the website into multiple HTML files, so I can work on each component separately without the file getting too confusing to look at. Then I would simply put every component together in one clearly structured HTML file.

But as I didn't want to learn a complex JS framework with tons of features that would slow down my website, I decided to make my own lightweight and robust framework.

Name

The original name of the script was really simple and overused component_loader.js. So I came up with this idea: As this framework makes your code clearer and more readable, I chose jasna.js for publishing it. Ясно ("Yasno") is the Russian word for "understand" or "clear" and it is pronounced like "Jansa".

Development

Selector

First of all, I had to figure out how to clarify which parts of the HTML document should be affected at all. First I had the idea to go through each <div> tag and check if it has the value "component" in the class attribute. But then I had a much better idea. I could just create a unique tag: <component>. This one is very easy to remember and you can find it more quickly in the code, so I decided on this. Also, this was very simple to implement:

var components = document.getElementsByTagName("component");

Now I had a collection of every element with this specific tag name.

Looping through

Now I had a collection of items. But I could only work with one at a time. So I made a simple for-loop to accomplish this.

var components = document.getElementsByTagName("component");
for (let i = 0; i < components.length; i++) {
    // do stuff with each components[i]
}

Source

Next, I had to come up with an Idea, how the developer specifies, where the component is stored at. In the first version of the script, I checked the id attribute for the URL of the source.

var url = component.id; //was a bad Idea

But later I figured, that it would be much smarter to check the src attribute. Like in <img src=""> or <script src="">.

var components = document.getElementsByTagName("component");
for (let i = 0; i < components.length; i++) {
    var url = components[i].attributes['src'].textContent; //beter idea
}

Fletching my teeth on fetching data

Now, for this part, I was overwhelmed, as I have barely used asynchronous functions before. I have used "threading" in Python before, which is roughly the equivalent of JavaScript's async functions.

At the end of watching several YouTube Videos, reading some documentations at MDN, and some tutorials. I have decided to go with XMLHttpRequest.
On W3Schools I found exactly what I was looking for:

var components = document.getElementsByTagName("component");
for (let i = 0; i < components.length; i++) {
    var url = components[i].attributes['src'].textContent; 

    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
           content = xhttp.responseText;
           components[i].innerHTML = content;
        }
    };
    xhttp.open("GET", url, true);
    xhttp.send();

}

At this point, the code was already working (I think). But I have decided to split the code up a bit because I wanted to implement live data. I made a separate function called load_component():

function load_content(component) {
    var url = component.attributes['src'].textContent;

    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
           content = xhttp.responseText;
           component.innerHTML = content;
        }
    };
    xhttp.open("GET", url, true);
    xhttp.send();
}

var components = document.getElementsByTagName("component");
for (let i = 0; i < components.length; i++) {
    load_content(components[i]);
}

You can see this in action on my webpage https://christian.remboldt.eu/index_original.html

Extra feature: "live_components"

At this point, you could only load everything just once. But I have decided, that dynamic content would be cooler. I won't go into details here, because the article is already really long.

Extending the for-Loop

for (let i = 0; i < components.length; i++) {
    load_content(components[i]);

    // If the first value of the class attribute is "refresh", then the component will be passed to a async function, which contains a never ending while-Loop
    if (components[i].classList[0] == "refresh") {
        live_components(components[i])
    }
}

Example:

<component src="https://examp.le/dynamic.php" class="refresh"></component>

The asynchronous function

async function live_components(component) {
    if (isNaN(component.classList[1])){
        // If the second value of the class attribute is not a number (NaN), the function will check every 2 seconds (2000ms) if the content is new
        sleep_time = 2000;
    } else {
        // If the second value is a number, then this will be taken as the "sleep_time"
        sleep_time = parseInt(component.classList[1]);
    }

    while (true) {
        await new Promise(r => setTimeout(r, sleep_time));
        load_content(component);
    }
}

Now you can specify in what time intervals it should check for updated content:

<component src="dynamic.php" class="refresh 10000"></component>
<!-- it will re-check every 10 seconds -->

Only reload the content if it has changed

function load_content(component) {
    var url = component.attributes['src'].textContent;

    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            content = xhttp.responseText;

            // I have added this if-condition, to check if the content of the component is already the same as the newly fetched content
            if (content !== component.innerHTML) {
                // Only updates if the source content has changed
                component.innerHTML = content;
            }
        }
    };
    xhttp.open("GET", url, true);
    xhttp.send();
}

Finished Code

jasna.js

function load_content(component) {
    var url = component.id;

    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
           content = xhttp.responseText;
           component.innerHTML = content;
        }
    };
    xhttp.open("GET", url, true);
    xhttp.send();
}

async function live_components(component) {
    if (isNaN(component.classList[1])){
        sleep_time = 2000;
    } else {
        sleep_time = parseInt(component.classList[1]);
    }
    while (true) {
        await new Promise(r => setTimeout(r, sleep_time));
        load_content(component);
    }
}

var components = document.getElementsByTagName("component");

for (let i = 0; i < components.length; i++) {
    load_content(components[i]);
    if (components[i].classList[0] == "refresh") {
        live_components(components[i])
    }
}

Live Example

As I said, I have written my website with the help of this little framework. And the index file looks really clear and readable. https://christian.remboldt.eu/index_original.html

<!DOCTYPE html>
<html>
    <head>
        <title>Christian Remboldt</title>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        <link rel="stylesheet" href="css/bulma.min.css" />
        <link rel="stylesheet" href="css/style.css" />
    </head>
    <body>
        <component src="components/navbar.html"></component>
        <component src="components/header.html"></component>
        <component src="components/about_me.html"></component>
        <component src="components/links.html"></component>
        <component src="components/bottom_part.html"></component>
        <component src="components/footer.html"></component>

        <script src="js/component_loader.js"></script>
        <script src="js/counter.js"></script>
    </body>
</html>

View JasnaJS on GitHub.