Recently I got interested in what it takes to implement simple web applications without the waggon-loads of framework software typically used these days. The following describes an approach which works surprisingly well.
Coming from a Java background, I feel much better when the compiler and, even more, the IDE can help to avoid stupid mistakes and moreoever tell me a lot about intent and functionality of variables and functions by showing their types.
Alas for what follows, using Typescript is not crucial. Plain Javascript can be used in the same way.
getElement():
HTML...Element
method to provide the root element of the DOM
part it manages.
onclick
event, but by providing
a selectElement()
method it allows other ways of
selection than by clicking.document.createElement()
to create the DOM
elements a component wants to directly manipulate. Do not use
template engines.<head>
element. This
allows style sheet links with rules that override the rules added by
the components.Right there. The whole idea is to get away without using tons of framework code. Just follow the simple guidelines outlined above.
It would obviously be nice if there were already components written in the style described above with well defined behaviour, independent of any framework, very scarce dependencies on other components, all interoperable because all they provide are a Javascript class each with a simple API to operate them.
At work we developed (web-)app software with Typescript, Angular, Cordova, Ionic and whatnot. It shall make things easier and development quicker and more reliable.
It works for the strong typing which Typescript brings in.
Angular and Ionic, on the other hand, were often the enemy we had to fight to get what we and/or the customer wanted. Both shall reduce complexity, but the price is (a) more complexity plus (b) magic.
If the magic just works, great. But too many times the magic didn't work as needed and we had to unravel the inner workings of magic to figure out how to nudge it to do what was required.
So I started to wonder whether the cost matched the value. Obviously this depends on so many factors that the outcome is different for everyone. Plus: no sane head of development would say these days:
"no frameworks, we use plain Typescript and the odd small library as long as it does not dictate our code structure, provides no magic and has a sane API to call."
Because there are always problems. With "industry standard" frameworks, the head of development can report: "this is normal, its complex software, there is a reported bug and BigCompany wants to fix it in release 1.2.3."
With custom made components, there is nobody to blame outside the company if shit happens. But for my private projects, I can do what I want, so I started to follow the guidelines above.
Works for me.
Historically a web page was just that, a page of HTML. Then came some active content, like Javascript, then came frameworks which allowed to pepper HTML templates heavily with code. Lets now finish this shift: don't write HTML at all, write pure Java-/Typescript.
These components need not
be Web
Components which extend from HTMLElement
or its
descendants. It works well if a visual component is represented by a
Javascript class which creates a small part of the DOM tree and
provides the root of the tree for other components to retrieve and add
to their own part of the tree. As an example lets assume we have
a ModalMenu
component and a component providing the
content to be shown, lets say FileMenu
. The code to use
these might look like:
const modal = new ModalMenu(...); const fileMenu = new FileMenu(...); modal.show(fileMenu.getElement());
The idea is that ModalMenu.show()
accepts
an HTMLElement
to inject into some wrapper, maybe
a HTMLDialogElement
and then shows in on screen.
To create the HTML elements, the component classes may use whatever
works best. But consider to just
use document.createElement()
calls, which has at least
the following benefits over templating (see more about
this below):
const button = documentCreateElement('button');the
button
immediately has the right
type, HTMLButtonElement
. When using a template
HTML structure a document.querySelector('#gooButton')
returns
Element|null
which is quite annoying in
Typescript. But even when using Javascript, the problem may
arise that the element ID in the template is changed
to #fooButton
which requires the insight to know
there is a query selector used to retrieve it and that code must
be changed too. And eventually, it won't.
createElement
we somewhat try to
avoid mushrooming of <div>
wrappers :-)Let the components add the CSS which is absolutely necessary for their
function to front of the <head>
element. Think of a
minimum size for an element to be tappable with fat fingers.
Non-functional styling for the good look should go into a normal style sheet. As long as it is loaded after the CSS injected into the head, the style sheet can still override the injected rules.
Each component should add a "unique" CSS class name to the root
element(s) it manages. Here everyone cries "foul" and that conflicts
are to be expected. Interestingly, though, fully qualified class names
in the Java world manage for decades to stay different from each
other with maybe very few exceptions. So if you start to develop a
library along the advice suggested here, you may start to mimic the
scheme and use class names
like de_mydomain_MenuButton
.
Similar to not having to much styling to generate a specific look, a component should also be scarce in which user interaction it provides via event bindings. If the user shall interact with some elements managed by the component, first add an API to the component under the assumption that other components want to initiate the interaction. Whether the component provides default event bindings or not can be decided afterwards.
As a result you will note that you naturally get two kinds of components: those with DOM elements and those which deal with event handling. It may well be OK to not separate these for small components. A button to initiate a foobleblarg is just that: a button with an action to take. No need to separate the action out.
But consider component MarliworgList
maintaining and
displaying a list of marliworgs. Deleting an element from the list
should be first and formost an API of the
class MarliworgList
. As should be the selection of list
elements. Which user interaction initiates the selection and deletion
of elements is a completely different story. Whether some button,
whether a mouse click, whether an incremental search box: there may be
many ways to select and delete elements and all of these
MarliworgList
andMarliworgList
.
Do not use templates.
([thing])=thang
, *if
and *for
and async
and pipes to create
template code which resembles HTML only when squinting really
hard.Just use document.createElement()
calls wrapped in
suitably designed (code wise) Type-/Javascript classes. Keep the DOM
elements you need to manipulate in class fields, stick the others
right into the DOM snippet the Javascript class is managing and forget
about them. Make use of the full power of Type-/Javascript for clean
code when creating DOM elements instead of weird template syntax.
See arrangimage
as an application where I used the approach described above. Ignore
that I use a class Template
, it is a relic of an
initial mistake which needs to be removed.