Goodbye HTML. Hello Canvas!
Part 7: Mosaic Generator & Releasing The Library
You can read the previous article here.

The Library
Finally, today I am releasing the GoodbyeHtml library.
This is the link for the library in GitHub.
The Application (Demo)
Today, I would like to explain more concepts, talk about the browser inside a browser, but I am a bit annoyed because of readers that keep replying that what I am doing is not possible, or that is too hard, or not performant… Therefore, I’ve decided to use the current demo and article to stop the nonsense.
This is the link to play the demo (NOT for smartphone).
Again, the library is a bit different from what was described before. While I don’t make a proper documentation, you can inspect the source files, I think they are very readable.
Again, the demo is a single HTML file with everything embedded. It keeps my server clean, but it is bad for studying. No problem, because you can easily study the source code of the demo in GitHub.
This is the link for the demo in GitHub.
The browser only sees one HTML element inside the document body: a canvas.
Inside this canvas there were built six different kinds of widgets: simple icon button, stage icon button, text button, checkbox, slider and text input box.
The code of the whole interface was written in a separate JavaScript file that counts less than 120 simple lines (excluding remarks and blank lines).
We will see the code of the file mosaic-interface.js part by part.
NOT FOR SMARTPHONES!
I’ve tested the demo using Chrome on Linux and on Windows, and everything is OK.
Then I tested the demo using Samsung browser on my Android smartphone. It worked but
- the browser doesn’t allow saving (pseudo download) the result
- the button “randomize” works but doesn’t change its look when pressed
Now the CATASTROPHE happens. I tested the demo on the same Android device, but using Chrome this time. The good news is that Chrome let us save the result. The bad news is as soon the first image is loaded, Chrome erases part of the panel “header” and part of the panel “options”, which means a HUGE BUG, because internally those panels are just images (painted canvases). Besides, they have very little or no relation to loading and processing an image.
This goes far beyond what I called browser idiosyncrasies in the first article of the series; which was ironized in the reply of a… clever reader.
Although this tool was projected for desktops only, I have never expected such weird behavior in (the same) smartphone.
The Interface Code — Initialization
Here we create the object that represents the library and we load the resources that the interface needs: font sheets and icons.
The loader must be created and called only once, exactly like the code shows, or else we will have synchronization problems. There is another function, for loading user images, when the application is running.
"use strict"
var goodbye = null
var box = null
function main() {
//
goodbye = createGoodbyeHtmlLibrary()
loadResources()
}
function loadResources() {
//
const loader = goodbye.createLoader()
//
loader.loadFont("sg", fontSmallGrey)
loader.loadFont("black", fontSourceProSansBlack)
loader.loadFont("white", fontSourceProSansWhite)
//
const keys = Object.keys(blackIconsSources)
//
for (const key of keys) { loader.loadImage(key, blackIconsSources[key]) }
//
loader.ready(resourcesLoaded) // must send a callback
}
function resourcesLoaded() { // the callback
//
initLogic()
//
mountInterface()
}
The Interface Code — Mounting
This simple function is the blueprint of the whole interface. First, it creates the box and assigns it to a global variable (constant). The box appends itself to the designed parent (the document body). Then, it defines the background color of the stage (choosing “transparent” is possible). And initializes the layers. We only need two. It hides the layer “help”. And commands the creation of five panels. That’s it!
function mountInterface() {
//
box = goodbye.createBox(1000, 600, document.body)
//
box.setBgColor("#084d6e")
//
box.initLayers(["base", "help"])
//
box.get("help").hide()
//
createHeader()
//
createSourceArea()
//
createResultArea()
//
createOptionsPanel()
//
createHelp()
}
The Interface Code — Panel Header

Here we create the panel “header”, set some decoration (including text) and apply 3 icon buttons to them. Note that the “help” button has a different behavior than the others. It has the “active” state.
function createHeader() {
//
const i = goodbye.allImages
//
const ph = box.get("base").createPanel("header", 0, 0, 1000, 50, "black")
//
ph.paintRect(0, 0, 250, 50, "white")
//
ph.setFont("black")
//
ph.write(30, 10, "Mosaic Generator")
//
ph.setFont("white")
//
ph.write(265, 10, "demo - web app made using only one HTML element")
//
const bl = ph.createButton("load", 790, 0, 50, 50, "rgb(0,150,180)")
bl.setImageNormal(i["load-black"])
bl.setOnClick(loadSourceImage)
//
const bs = ph.createButton("save", 865, 0, 50, 50, "rgb(0,150,180)")
bs.setImageNormal(i["save-black"])
const disabled = goodbye.fadeImage(i["save-black"], "black", 0.40)
bs.setImageDisabled(disabled)
bs.disable()
bs.setOnClick(save)
//
const bh = ph.createButton("help", 940, 0, 50, 50, "rgb(0,150,180)")
bh.setImageNormal(i["help-black"])
const negative = goodbye.negativeFromImage(i["help-black"])
bh.setImageActive(negative)
bh.setOnClick(buttonHelpClicked)
}
This is the code for the stage icon button:
function buttonHelpClicked() {
//
const button = box.get("base.header.help") // stage icon button
//
if (button.getActive()) {
//
button.deactivate()
box.get("help").hide()
}
else {
//
button.activate()
box.resetFocus()
box.get("help").show()
}
}
NOTE: the code of the library is highly encapsulated in functions. For example, we don’t say my_panel.visible, we say my_panel.getVisible().
The Interface Code — Panel Source Area

This is very simple: a panel without widgets. It is just a substrate to paint images on.
function createSourceArea() {
//
const ps = box.get("base").createPanel("source", 20, 70, 600, 400, "red")
//
const chess = goodbye.createCheckerboard(600, 400, 50, 50, "silver", "gainsboro")
//
ps.paintImage(chess, 0, 0)
}
The Interface Code — Panel Result Area

This is almost a copy of the previous panel.
function createResultArea() {
//
const pr = box.get("base").createPanel("result", 670, 120, 300, 300, "red")
//
const chess = goodbye.createCheckerboard(800, 450, 50, 50, "silver", "gainsboro")
//
pr.paintImage(chess, 0, 0)
}
The Interface Code — Panel Options

This is the most complex panel of the application, considering the amount (4) of widgets and considering the amount (4) of different kinds of widgets. But, surprisingly, its code is short.
function createOptionsPanel() {
//
const po = box.get("base").createPanel("options", 20, 500, 960, 80, "lightgrey")
//
po.setFont("sg")
//
po.createCheckbox("stretch", 10, 10, 14, true, null)
//
po.write(30, 10, "stretch both dimensions when loading")
//
const br = po.createButton("randomize", 90, 35, 120, 30, "white")
br.config("sg", "randomize", randomize)
br.disable()
//
po.write(418, 10, "size of resulting mosaic")
//
paintMosaicSize()
//
const ss = po.createSlider("size", 400, 30, 200, 30, 0)
//
ss.setOnChange(changeMosaicSize)
ss.disable()
//
po.write(772, 10, "choose mosaic name")
//
const tn = po.createTextbox("name", 760,30,150,35,15, "black", true)
tn.setText("my-mosaic")
}
These are its handlers:
function changeMosaicSize() {
//
const factor = box.get("base.options.size").getValue()
//
mosaicDimension = 300 + (Math.floor(1500 * factor / 300) * 300)
//
paintMosaicSize()
}
function paintMosaicSize() {
//
const panel = box.get("base.options")
//
panel.clearRect(390, 60, 100, 20)
//
panel.write(400, 63, mosaicDimension + " x " + mosaicDimension)
}
The Interface Code — Panel Help

Another very straight forward panel, with no widgets, only text.
function createHelp() {
//
const ph = box.get("help").createPanel("help", 0, 50, 1000, 550, "lightgrey")
//
ph.setFont("sg")
//
ph.write(150, 50, "This is a desktop application.")
ph.write(150, 100, "Loading an image creates a mosaic based on its center.")
ph.write(150, 150, "Pressing \"randomize\" creates another mosaic based on a random point of the image.")
ph.write(150, 200, "There is only a single HTML element (a canvas) inside the document body.")
ph.write(150, 250, "All other widgets were constructed inside this single canvas and the browser doesn't know about them:")
ph.write(170, 280, "single icon button, stage icon button, text button, checkbox, slider and text input box.")
ph.write(150, 330, "The code of the *interface* of this demo has less than 100 meaningful, simple lines and was written")
ph.write(170, 360, "using the GoodbyeHTML library.")
ph.write(150, 410, "The library is available at https://github.com/JoanaBLate/goodbye-html")
ph.write(150, 460, "Clicking again the help icon hides this help.")
}
We are done now!
Getting Element By ID
Having the box as a global variable (constant) is very helpful and make source code lighter and cleaner. There is no problem about having many boxes in a web page. Of course, we need a different global variable for each box — box, box2, bigBox….
We can access any layer, panel or widget at any time using the function box.get(id).
There are only 3 formats for ID:
- for layers: layer
- for panels: layer.panel
- for widgets: layer.panel.widget
We create each panel or widget providing only its name; the library creates its ID using the formula parent.id + dot + choosen_name.
// creating the panel
const po = box.get("base").createPanel("options", ...// creating the widget
const ss = po.createSlider("size", ...// acessing the widget in other function
const factor = box.get("base.options.size").getValue()
Conclusion

The real application we made today proves that the canvas based approach for a web page works very fine. But, as we have seen along this article series, in more than 99% of the cases, it is not supposed to replace the HTML/CSS approach.
I think I can hear you:
It is a pity, because constructing a web page with the Goodbye library is soooooo cool and easy. I would like to use it EVERYWHERE! Also, I wouldn’t like to lose any benefit that the HTML has, like fluid layouts, accessibility prone, (scalable) TrueType fonts, automatic translation…
Well… a good person doesn’t pass a sausage on a dog’s nose and warns, It is just for smelling. Right?
Please, read the article below to the end or, at least, read its end. The solution is there.
Hey, HTML… I Am Back, Darling!
Stay Tuned
This is the link to the next article of the series.