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 `<` in Response
replace `>` with `>` in Response
replace `%09` with ` ` in Response
replace ` ` with ` ` 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)
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 😊