DEV Community

Cover image for MVC, AJAX and REST - Breaking out of the sandbox, part 4
Graham Trott
Graham Trott

Posted on

MVC, AJAX and REST - Breaking out of the sandbox, part 4

In Part 3 of this series we built a simple File Manager in a mixture of JavaScript in the browser talking to Python in an embedded server. Here's another approach to the same solution; one that uses virtually no JavaScript. Instead, the browser code is written in a high-level scripting language called EasyCoder, that is itself written in JavaScript. It's designed to do the kinds of jobs normally done by JavaScript in the browser but it's a lot easier for beginners to understand.

The main reasons to consider using a high-level scripting language - or even building one yourself - are readability and maintainability. The kind of things going on in a browser relate directly to what users see and do on their computers. This is very unlike server code, which users never have any contact with. On virtually any project, the user interface will undergo more need for maintenance than any other part of the system. Maintenance people are often not specialists; they have a wider range of duties than just development. In addition, while development is intensive and short-term, maintenance is intermittent and long-term.

The software industry is driven by an on-going desire to increase the power of software tools, but unfortunately this nearly always increases complexity at the same time. Increased complexity reduces the availability of people who truly understand the products, so although as individuals they may become more productive they join a shrinking cohort. A gap is widening between the waves of sophisticated new software tools coming from the leading edge of the industry and the needs of people who build and maintain everyday products, who are not highly-trained specialists. It's in this gap that simpler solutions are needed, even if they don't offer the same levels of ultimate performance.

It's hard to get skilled in the first place and it's harder still to hold onto skills unless they are used regularly, so the simpler we can make the coding of our user interfaces the better. It contributes to lowering the cost of maintenance and improving long-term product reliability.

Try it out

In order to avoid simple mistakes, typos etc, the complete set of files for this article can be downloaded from the EasyCoder GitHub repository. Once unzipped, all the files are included in a single folder called dev.to that you can place anywhere convenient. (Edit: If it's not obvious, the system is started by running fileman.py. Also, those not running Linux - that's probably nearly everyone - may have to edit the command at the top of fileman.py that calls the browser.)

The File Manager, version 3

As in the previous versions we start with an HTML file. Let's call it index-ec.html:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>MVC Demo</title>
        <script type='text/javascript' src='easycoder/plugins.js'></script>
        <script type='text/javascript' src='easycoder/easycoder.js'></script>
    </head>
    <body>
        <pre id="easycoder-script" style="display:none">
            script Boot
            variable Script
            rest get Script from `./scripts/fileman.ecs`
            run Script
        </pre>
    </body>
</html>

The important components are two JavaScript files in the HEAD and a short script in the BODY. The easycoder.js file is the engine that runs EasyCoder scripts. When the page loads it looks for a special element with the id easycoder-script then compiles and runs it.

The plugins.js file defines which language features should be available for EasyCoder to use. This needs to be tailored for the system it runs in; the following version (included in the download) requests the 3 plugins browser, json and rest.

const EasyCoder_Plugins = {
    getGlobalPlugins: (timestamp, path, setPluginCount, getPlugin, addPlugin) => {
        console.log(`${Date.now() - timestamp} ms: Load plugins`);
        setPluginCount(3);
        getPlugin(`browser`, `./easycoder/plugins/browser.js`, function () {
                addPlugin(`browser`, EasyCoder_Browser);
            });
        getPlugin(`json`, `./easycoder/plugins/json.js`, function () {
                addPlugin(`json`, EasyCoder_Json);
            });
        getPlugin(`rest`, `./easycoder/plugins/rest.js`, function () {
                addPlugin(`rest`, EasyCoder_Rest);
            });
    },
    rest: () => {
        return ``;
    }
};

The code in the BODY of our HTML is a simple boot script that loads another script (called scripts/fileman.ecs) and runs it. It's quite feasible to put the entire file manager script into the HTML file, but keeping it separate is better practice as it allows a different script to be substituted with no more than a name change. A typical project may make use of several EasyCoder scripts at the same time, but these are usually self-contained so a change to one has little or no effect on others. The only time they even know of each others' existence is when messages are passed between them, and even these reveal nothing about what goes on inside them.

The fileman.ecs script does the same job as its JavaScript counterpart and it looks like this:

!   File Manager

    script FileManager

    h2 Header
    div Panel
    div Row
    a DLink
    a FLink
    variable HOST
    variable Request
    variable Response
    variable Current
    variable Dir
    variable Dirs
    variable Files
    variable Name
    variable Extension
    variable N
    variable X
    variable Binary
    variable CallStack

    put `http://localhost:8080` into HOST

    put empty into Binary
    append `.png` to Binary
    append `.jpg` to Binary
    append `.gif` to Binary
    append `.mp3` to Binary

    create Header
    set the style of Header to `text-align:center`
    create Panel
    set style `background` of Panel to ` #ffffee`
    set style `border` of Panel to `1px solid gray`
    set style `font-family` of Panel to `mono`
    set style `margin-left` of Panel to `auto`
    set style `margin-right` of Panel to `auto`
    set style `padding` of Panel to `0.5em`
    set style `width` of Panel to `90%`

    put empty into Current
    put empty into Dir
    put empty into CallStack
    history set

ListFiles:
    if Dir put Current cat `/` cat Dir into Current
    append Current to CallStack
    on restore
    begin
        put the json count of CallStack into N
        if N is less than 2 stop
        take 1 from N
        json delete element N of CallStack
        take 1 from N
        put element N of CallStack into Current
        go to LF2
    end
    history push
LF2:
    put HOST cat `/listfiles` cat Current into Request
    rest get Response from Request
    put property `dirs` of Response into Dirs
    put property `files` of Response into Files

    if Current put Current into Dir else put `/` into Dir
    set the content of Header to `List of files in ` cat Dir

    clear Panel
    set the elements of DLink to the json count of Dirs
    put 0 into N
    while N is less than the json count of Dirs
    begin
        put element N of Dirs into Name
        if left 1 of Name is not `.`
        begin
            create Row in Panel
            index DLink to N
            create DLink in Row
            set the content of DLink to Name
        end
        add 1 to N
    end
    create Row in Panel
    set the elements of FLink to the json count of Files
    put 0 into N
    while N is less than the json count of Files
    begin
        put element N of Files into Name
        if left 1 of Name is not `.`
        begin
            create Row in Panel
            put the position of the last `.` in Name into X
            put from X of Name into Extension
            if Binary includes Extension set the content of Row to Name
            else
            begin
                index FLink to N
                create FLink in Row
                set the content of FLink to Name
            end
        end
        add 1 to N
    end
    on click DLink
    begin
        put the content of DLink into Dir
        print `Directory: ` cat Dir
        go to ListFiles
    end
    on click FLink
    begin
        put the content of FLink into Name
        print `File: ` cat Name
        append Current to CallStack    
        go to ViewFile
    end
    stop

ViewFile:
    put HOST cat `/readfile` cat Current cat `/` cat Name into Request
    rest get Response from Request
    set the content of Header to `Content of ` cat Dir cat `/` cat Name
    replace `<` with `&lt;` in Response
    replace `>` with `&gt;` in Response
    replace `%09` with `   ` in Response
    replace `   ` with `&nbsp;&nbsp;&nbsp;` in Response
    replace `%0a` with `<br>` in Response
    set the content of Panel to Response
    stop

A quick scan of this should give a lot of clues to what's going on, even to people with little programming experience. In places the details will need clarification; if you are interested you can find a complete tutorial and programmer's reference in the EasyCoder Codex (to see the programmer's reference click the book symbol at the top of the right-hand panel). I present the above to demonstrate that it's possible to code in something close to English, allowing any programmer to understand what's happening without even knowing the language.

One point to note; this version has a working browser 'Back' function. Even after examining how EasyCoder does it I've been unable to get my code in Part 3 to do the same, which goes to show how much harder it is to code some things in JavaScript than in a higher-level alternative that provides all the features you need. (The feature in question is handled here by history set, history push and on restore.)

To launch this version, either modify the Python launcher URL:

cmd = f'chromium-browser {HOST()}/index-ec.html'

or if the server is already running add a tab and set it to

http://localhost:8080/index-ec.html

This is the end of my series on 'Breaking out of the sandbox', in which I have demonstrated different ways to leverage the power of the browser to seamlessly implement a GUI for another language, or conversely to use a local REST server to provide otherwise-forbidden features to browser applications.

Postscript

Since I wrote these articles in the summer of 2019 I discovered WebSockets, which offer a simpler and more streamlined solution than REST for certain types of application, including my file manager. At some point I hope to write another article showing how WebSockets can be used instead of a regular HTTP server like Bottle.

About me

Readers of this and my previous articles will have figured by now that I am a regular advocate of simplicity. This is because it's the only way I know to deliver results that work properly and can be maintained. I am not exceptional: I think of myself as a "bear of little brain" (that's a Winnie the Pooh reference). Like many people I have major difficulty reading complex code, particularly if it uses a lot of symbols or relies on any kind of framework to do its job. If I can't find the equivalent of main() and work from there I go into panic mode. On the other hand, I love language for its richness and versatility. Perhaps I shouldn't be a computer programmer at all, but I seem to get results and it's now way too late to change. EasyCoder was born out of a combination of laziness and impatience - an unwillingness to spend months learning how to do things properly before being able to verify that an idea is even possible. Cutting corners does sometimes work, however much tut-tutting and disapproval comes from elsewhere. Often it's by breaking the rules that we discover something that's better because it wasn't constrained by what went before.

Photo by Markus Spiske on Unsplash

Top comments (1)

Collapse
 
anwar_nairi profile image
Anwar

I love the fact that you provided another perspective of such a complex web app using EasyCoder. It serves well the purpose of this tool, to simplify the comprehension and maintenance of any web app.

Keep up the good work, looking forward for a web socket introduction 😊