Add old posts

This commit is contained in:
Talia 2021-10-24 11:46:53 +02:00
commit e95a1467b6
No known key found for this signature in database
GPG key ID: AD727AD22802D0D6
4 changed files with 622 additions and 0 deletions

189
Rants/Lua over Ruby.md Normal file
View file

@ -0,0 +1,189 @@
---
title: Why I prefer Lua to Ruby
published: true
tags: Lua, Ruby
date: 20190401
---
So Lua and Ruby are two rather similar languages in many aspects. There's a lot of articles explaining what makes them so similar and many of the more superficial differences.
Today I'd like to point out some reasons that have shaped my opinion on the two languages.
I will risk sounding like a hater, but I'd like to point out that I don't particularly dislike Ruby. There's many positive things about it, but since I do prefer Lua, I just chose to point out some of its problems today.
# Speed
It'd be easy to just say "Lua fast, Lua good" and be done with it. In reality though, things aren't always that easy.
### File Size
The thing about Lua is, it's very small. Not only does this mean that it fits almost everywhere, but also that it loads very fast
```bash
> time ruby -e 'puts "hello"'
hello
real 0m0.059s
user 0m0.054s
sys 0m0.005s
> time lua -e 'print "hello"'
hello
real 0m0.003s
user 0m0.003s
sys 0m0.001s
```
This only makes a difference when you're calling the executable many times in a row, but the difference does add up over time.
----------------------
### Functions vs. Methods
The next thing to consider is that, in Lua, functions are stored in variables, and are therefore easy to look up.
Ruby, on the other hand, has to look up methods for everything. For short methods (let's say, adding two numbers) this can mean a significant overhead.
```bash
> ruby -e 'puts self.class.ancestors'
Object
Kernel
BasicObject
```
Even the main object every ruby script runs in inherits from 3 ancestors.
It should be noted that Lua isn't immune to this problem. When writing object-oriented code, this also ends up happening. The difference is that in Lua the programmer must do this explicitly, while in Ruby it happens all the time.
----------------------
### Strings / Symbols
~~This may surprise some people, but Lua actually has a huge disadvantage to Ruby in terms of speed: It has no Strings in the way Ruby has them. All strings in Lua are automatically interned. Ruby has interned strings as well; it calls them *Symbols*.~~
~~This has its upsides and, overall, was probably a smart decision, but it also means reading in a long text file takes much longer in Lua. Imagine calling `to_sym` on every line in Ruby.~~
`<edit>`
Since writing this post, I have double-checked this and discovered that I apparently misunderstood this or just remembered it incorrectly: Lua *does* intern all **short** strings by default, however, above a certain length, this doesn't happen anymore. Strings that are too long for interning don't get hashed immediately, but they do get hashed once it becomes necessary (for example when comparing them to another string or indexing a table) and will from then on use just use that hash.
This pretty much means that, in most cases, Lua wins over Ruby even when working with long (but immutable) strings.
However, the downside that Lua strings are immutable remains, and modifying a string means creating a new, modified copy of the string.
`</edit>`
----------------------
### Vararg functions
If I was asked which of the two languages had the powerful implementation of variadic functions, I'd 100% say Ruby. You can mix and match many kinds of syntactic constructs that capture additional arguments into both arrays and hashes.
But, with great strength comes... not so great performance.
Let's consider the following piece of code:
```ruby
def foo(bar, *rest)
do_something(bar)
do_something(*rest)
end
```
Every time the foo method is called, ruby needs to instantiate a new array `rest` and collect the excess arguments into it. This ends up happening a lot and makes variadic methods very unsuited for code that needs to perform well.
Consider the equivalent code in Lua:
```lua
function foo(bar, ...)
do_something(bar)
do_something(...)
end
```
Here, there's no array involved. The function can just leave bar on the stack, call `do_something` telling it it has 1 argument, then pop bar from the stack and call `do_something` again, telling it how many items are left on the stack.
This means writing functions like this is way more viable even when your code needs to run as fast as possible.
----------------------
### Mentality
One difference that probably makes a way larger difference than most people would assume is the difference in mentality between the two communities.
When asked why Ruby is so great, the general response from its community will be something along the lines of "Because it's fun to write!". Many Ruby examples out there seem to throw performance out the window before even starting. This isn't necessarily a bad thing, but it ultimately leads to people writing libraries that run way slower than they could.
My experience with the Lua community has been vastly different. A simple google search brings up way more relevant and detailed information on how to improve performance in Lua than in Ruby, even though the latter has a much larger community that also seems to write way more about it.
----------------------
### LuaJIT
Lua is already very fast on its own. LuaJIT though, that's a completely different level. There have been examples where JITed Lua code can even run faster than equivalent C code, because the compiler sometimes has more awareness of the code it's optimizing (After all, it keeps track of the code as it's being executed)
Ruby has made a huge step in the right direction with its own JIT Compiler, but that's still nowhere near the performance improvements of LuaJIT when compared to PUC Lua, the "reference" Lua implementation.
-------------------------------------
# Simplicity
Leaving performance aside now, there's another reason why I consider simplicity a very positive feature of Lua: It's not only easy to learn, but also easy to master.
### Modularity
This is probably one of the things I hate the most about Ruby. No matter how hard you try, you will never get rid of global state. At the very least you will have some modules and classes polluting the global environment, and there is no way to load a library into a contained space.
This means, for example, that it's not possible to load two different versions of one library without having to go through its source code and renaming things everywhere.
```ruby
require 'some_class'
foo = SomeClass.new # Where did this come from?!
```
In Lua, on the other hand, you can just rename the file and load it into another local. How things are called internally doesn't matter to the host program.
```lua
local someclass = require 'someclass'
local foo = someclass.new()
```
----------------------
### Side effects
Ruby tries to be as comfortable as possible for the developer. I often find myself wishing it didn't
Consider the following example:
```ruby
require 'missile_cruiser'
MissileCruiser.new.last_fired
```
What happens there? One would assume, the variable `last_fired` will return the last missile that was fired. Since we didn't fire one, it would be reasonable for this to be `nil`.
But wait, what if it's a method?
Maybe it will raise an error because we didn't fire any missile yet?
Even worse, maybe some developer thought if we wanted to know about the last missile, we probably wanted to fire one, so the method just fires a missile and returns that one?
This kind of thing happens often in Ruby. The lines between what is a value and what is code that gets executed are blurred.
----------------------
### (in)Consistencies
Try the following code in `irb`:
```ruby
puts 1/0
puts 0/0
puts 1.0/0
puts 0.0/0
```
Things like that make a language (and, by extension, programs written in it) harder to understand at first glance, specially to newcomers. No language will ever be 100% consistent, but these inconsistencies should still be reduced whenever possible.

75
Rants/Rant on Change.md Normal file
View file

@ -0,0 +1,75 @@
---
title: A rant on change, and the good old times.
published: true
description: A long and poorly structured rant about a series of harmful phenomena I've observed in the world of programming.
tags: rant, programming, community
date: 20201125
---
# Preamble
Over the last decade or so, I've had a peek into quite a few developer communities. I do most of my private programming in Lua, and interact with that community almost daily. At work I use Ruby, and often get a good glimpse of that community as well. Through Hacker News, Reddit and a bunch of other sites I get a good impression of many different "general" programming communities. I am only human, so my opinions will of course be biased, but for what it's worth, I do believe that I have at least some degree of objective perspective on this topic.
# The Problem
Lately, I've noticed a bit of a trend within the world of software development. Put bluntly, it seems to me like the field of programming is slowly but surely drifting down the hillside, spearheaded but not exclusively caused by the web community in particular.
# The Symptomatology
What do I mean by this though? Well, starting with the a parallel, but mostly positive process: Programming is becoming more and more inclusive and easy to get into. Elitism is slowly becoming more of a meme than an honest feeling of superiority, and more and more people are picking up programming or closely related skills.
However, in the process of wiping away elitism and gatekeeping in the software world, it seems to me, that in our excitement we are also throwing out valuable barriers that keep our world stable and nice
One of the big changes I have noticed is a general shift from "hobby that you can even get paid for" to "easy way to land a comfortable job". Discussions about fancy things you can do with software for the sake of experimentation are being replaced with top ten interview questions for whatever the hottest new technology is at a given moment. Enthusiasm for technologies despite its economic viability is being replaced with amusement at getting something to work as quickly (read: wasting as little paid hours) as possible.
The result is that these days, there's a much bigger emphasis on learning products over theories. The "brand" is slowly sneaking into the world of programming, and while specific technologies have always been something you'd learn, it used to be more about languages than single platforms. Maybe this started with Java and the JVM platform, but I'm not old enough to judge that.
At the same time, some of the blame might fall on the internet. In the "good old days", when "everything was better™", the lack of a centralised knowledge-base meant people had to learn their IT skills though manuals and text-books. One could consult a manual for certain questions, but given the limited space, this would only lay out the components and tools you'd need to fix a problem. These days, one can consult the internet with a very specific question and is likely to get a solution for that exact problem spoonfed by the collective knowledge of the entire programming community.
Is this bad? No. But it leads people to rely on it, and *that* certainly *is* bad. One of the reasons I have stopped visiting the ProgrammerHumor subreddit despite enjoying most of the bad and repetitive jokes there, is that a certain category of self-deprecating humor has gotten so overused, that it's impossible to scroll though it for 5 minutes without seeing at least one post where the punchline falls along the lines of "all programmers do is google" or "if stackoverflow is offline, all development just stops worldwide".
By itself, each one of those jokes would be funny. Programming *does* sometimes feel like most of what one does is googling, specially when just learning a new technology. But the obsession with this narrative, over time, has started to make me wonder if this is what some people actually believe, and, by constantly repeating and repeating and repeating this same punchline, if newcomers and outsiders will sooner or later get the impression that this is the unironic reality of programming.
Why does this matter? Consider the public image of programming. Currently, most people, when I tell them I do programming, react with at least some degree of respect. "He's a programmer, he must be smart" is what some people might think, not unlike someone calling themself a "scientist" or a "mathematician". Programming is considered an intellectual discipline. This directly translates into how much worth is associated with the job: if you have to be smart to do something, you will get paid well, because you're "in demand". What, did you believe we're getting paid more than a janitor, who spends most of their day doing physically demanding work, because our job is so much harder? You probably know that that's not how it is.
So from that perspective, constantly reiterating the idea that programmers are just code-monkeys that only google things is, to put it bluntly, strategically stupid.
# The Mythology
The idea that programmers just copy together all of their code is far from the only harmful idea out there though. Parallel to it, a much more harmful myth is that "everyone can code [on the same level]". Now, just as with drawing, music and many others, I do believe that everyone, without exceptions, is capable of learning the basics of programming.
However, it is simply a fact that not everyone has the talent to program something like the linux kernel. And this is probably one of the hardest truths to properly internalize: we don't want to be elitists, we want everybody to have a chance at joining our community. We want others to discover and enjoy our hobby, just as we did. And that's fine. I won't stop telling people that they can *learn to code*. But I don't want to keep spreading the lie that everybody can become just as good as everyone else. Some people don't have what it takes, and some people have talent that I just won't be able to reach myself.
So how does that persistent myth do any real harm? In the same way the Google and StackOverflow jokes do: by devaluing our skills and our work. If everyone can do a thing, then there's nothing valuable about it. If anybody can go to a boot camp and become a super-duper good programmer within a year or two of job experience, then there's nothing worth respecting about anything one can achieve.
On the other side, it sometimes seems that employers still haven't given up on the mythical plug-n-play developer that can be employed today and start being 100% effective tomorrow. It used to be that OOP promised developers who could be interchanged without any effort, and instead of just disappearing, this supersticion seems to just have shifted to the ridiculous belief that a developer can specialise in a specific tech stack to the degree where they can just be sat down before a project and they'll instantly know how to make the product better.
# The End Game
Is all of this coincidence? Well, it probably is. I don't believe there's any mastermind about us programmers shooting our own leg and devaluing our own skills. However, there is someone who profits from it, and certainly has good enough reasons to help the process along occasionally.
I am, of course, talking about employers. There used to be a myth of software systems that don't need programming. Where anybody could just drag a few boxes around and the result would be a program that does exactly what it needs to, no matter how complex the task. Unfortunately for employers, and very fortunately for us employed and employment-seeking programmers, this never happened, and we've somewhat accepted that it just won't happen anytime soon.
What other hope is there to not have to pay well-trained and experienced professionals for their expensive time? The simplest answer is: make their time less expensive.
While observing the community discuss employment practices, specially in the USA, I'm routinely baffled at how comically and artificially imbalanced this whole process is.
More than enough people have written lengthy articles about how pointless and arbitrary the distinction between "junior" and "senior" developers often is, and have done a much better job than I could, considering this is not all that common in my country. I also often read about interview processes that just seem utterly lacking in any sort of respect for the applicant and their time.
The strategy seems obvious: gasslighting us all into believing we're of very low, if any, worth to the employers who so generously offer to grant us shelter from our own lack of practical skills. We're not corporate simps, we're just grateful that we don't have to sleep under a bridge. I am exaggerating, of course, but it does seem very clear that this is the general direction things are going. It's not a problem specific to our field either, but it might be more pronounced because of the rapid influx of newcomers, and the arcane image to the outsider.
While everybody can estimate how much skill and effort goes into, say, building furniture, it is very hard if not impossible to the uninitiated to get a feeling for how difficult it is to build a software product. It seems almost like black magic to the outsider, and this is another thing that people often joke about (sadly, the punchline is often that ultimately, we're not doing anything at all difficult).
# The Remedy
It is hard to say what could be done to push programming back up the hill. There seems to be a strong culture of self-deprecation (probably adopted from broader nerd-culture), and a tendency to unironically under-value ones own skills in this community. The amount of posts I find about impostor syndrome every week on its own already indicates that we have a serious problem that needs to be addressed.
At the same time, our attempts to be kind to everyone might be pushing us further into the corner. Maybe we should develop a bit more of a "git gud" (`git: 'gud' is not a git command. See 'git --help'`) attitude as the gaming world (sans the insults and trash-talking, if possible), that puts more emphasis on showing someone how to improve than to defuse their insecurities and feelings of insufficiency.
As for the focus on finding employment, I really think that needs to be toned down. Yes, it is easy to be excited about potentially turning your hobby into your job, or switch careers to something you found to be much more enjoyable. For some, programming might even be a unique chance for a better life. But for many of us it is also a hobby, and for me personally, it is as much an art as is music or drawing. I don't care about top 10 javascript interview questions and would much rather talk about all the totally useless and impractical but technologically cool and creative things you guys have built.
And please, don't sell yourselves under value. Programming is hard. Everyone can learn it, but few can master it. We're not just code-monkeys copying from google. And if you're learning: Try having fun and don't worry too much about maximising your employability.
# In Conclusion
It's not like I think the world will end. Programming won't stop being my hobby, nor do I fear I some day won't find a job (at least not before someone develops an AI that does all the programming for us and understands human language). I want the world of programming to stay this quirky place, where people build cool stuff, share what they've built and discuss it with others. Ultimately I hope we can all have fun programming 🧡

77
Rants/Tabs over Spaces.md Normal file
View file

@ -0,0 +1,77 @@
---
title: Tabs are objectively better than spaces, and here's why
published: true
description: Here's why I think tabs are objectively better than spaces
tags: indentation, tabs, spaces
date: 20210215
---
# The Debate
This is probably one of the longest ongoing bikeshedding debates in the programming community: Should we indent our code with **Tabs** or with **Spaces**?
In this post, I will do my best to explain why **tabs** are the right choice, not only in my personal opinion, but objectively.
# Indentation
First of all, I want to define the scope of my argumentation: I am referring to **indentation**, not **alignment**.
The former depends on semantics, the latter on word lengths. For obvious reasons, using tabs for alignment is not possible; whether alignment should be a thing at all (hint: it shouldn't) or how it should be achieved is a different debate and irrelevant for this article.
# Semantics
The most puritan argument for tabs is probably the semantic information they add to the code. I have never seen a real-world example where this matters, but as programmers we often like to obsess over using the right "thing", be it a HTML element or an ASCII character.
Just as with HTML, I suspect this might be relevant for users of screen-readers; if so, I'd like to hear about that in the comments.
# Consistency
The main argument I've heard defending spaces is that code looks "consistent" everywhere. Whether you post your code on Stack-Overflow, GitHub Gists, or on your blog; it will always be indented by the same width.
In the next section I will explain why that is actually a bad thing, but first I'd like to point out that this doesn't have any benefits either.
Code is not visual art. The indentation width doesn't alter the codes meaning in any way and, unlike with alignment, changing it doesn't break the visual layout of the code.
There is no reason whatsoever why another user reading my code on the internet should read it with the same indentation width that I wrote it in.
# Customizability
On the contrary: forcing the reader to use the same indentation width as the author only takes away from their ability to read the code the way they want.
As an example: I personally prefer a tab-width of 3 spaces, because 2 is just barely too short to follow through deeper nested blocks of code and 4 is one more space-width than I need, and thus one wasted character of line-width per indentation level.
So the question is: Why would *you* have to read my code with such an awkward tab width, just because to my uncommon taste that seems like the right value?
The answer is, of course, you shouldn't. You should be able to read *my* code the way *you* prefer to read it.
Just like we don't expect readers to use the same editor, with the same colour-scheme, the same font and the same font-size as us, we shouldn't expect anybody to use the exact same indentation-width.
# Accessibility
So far I've looked at customizability as a convenience feature. I *like* 3-space indentation more, so I *want* to read code that way.
What I haven't mentioned is how for some programmers, the right indentation width can make a huge difference in how well they are even able to read the code.
There have been countless posts of visually impaired developers. For some, 2 spaces is just not enough indentation, making it unnecessarily hard to read code, others might need to use very large font sizes, and prefer shorter indentations to save screen space.
And as I mentioned above, I don't even know how well screen readers deal with spaces as indentation, and would love to hear about it of anybody reading this has any experience with that.
# Consistency (again, but differently)
This is by far the most ridiculous reason, or group of reasons people make to argue for spaces:
Projects indented with tabs, so the claim, cause additional work when people contribute code that is indented with spaces, requiring additional moderator intervention.
Needless to say, this argument works exactly the same both ways, and if anything, says more about the typical space-user than the typical tab-user.
Regardless of preference, in the age of linting tools and CI pipelines, this is just not an issue anymore. We can automate the process of checking indentation, or even have it fixed automatically.
# Conclusion
There is not a single good reason to prefer spaces over tabs. The whole space-indentation mythology is nothing but hot air and false claims.
-----
Do you disagree with my position? Let me know!
I'd love to hear additional arguments for or against spaces in the comment section :D

View file

@ -0,0 +1,281 @@
---
title: Building an HTML Type-Writer
description: "A detailed description of the development process of an HTML TypeWriter element in plain JavaScript using the custom-element API, meta-programming and some recursion magic."
published: true
tags: type-writer, html, javascript, webdev, async, metaprogramming
date: 20210911
---
## Backstory
A few weeks ago, I built a simple type-writer web component: a user would load a JavaScript module and define a `<type-writer>` element with several lines of text. The component would then type out the first line of text, delete it again and rotate it to the back of the list.
```html
<type-writer>
Here's a first line
And here's another
Only plain text supported
</type-writer>
```
This worked well for simple cases, but it was obviously very limited. What if you wanted one word to be emphazised? Or have a line-break? The obvious solution was to extend this component to reproduce an entire DOM-Subtree.
Fast-forward to today, and I find this very nice [article](https://dev.to/auroratide/a-typewriter-but-using-a-new-html-tag-60i) on Dev.to showing off a custom component that does just this. This made it clear: I couldn't just leave my component as it was; it had to be brought to the same level. And so I started coding...
My aim for this article is to use this example to explain some of the nicer things one can do with web components without using any frameworks or libraries. Just one file of JS that runs out of the box in any modern browser.
I'll be trying to reproduce my thought process to some extent, but won't be following the same timeline of when I built this, since features got added piece by piece as it tends to be with programming, and I'd much rather group them by the concepts they rely on.
## Async
One of the first things I figured out while building this component was that object methods can be `async`. Of course they can, they're just functions, after all. But for whatever reason, I didn't expect to be able to just put `async` in front of a normal method definition, but apparently that works perfectly well.
So why is this important? Well to slowly type out some text, one needs some sort of loop that can suspend its execution. Yes, one could also work with callbacks or promises, but it's 2021 and I just don't see a reason not to write blocking-style code instead using `async` and `await`.
So that's the starting point: an `async run()` method that contains the main animation loop.
Since the only thing worth `await`ing in this case is a timeout, I needed a wrapper around `window.setTimeout`, which I put at the top of my file:
```js
const sleep = time => new Promise(done => setTimeout(done, time*1e3))
```
No magic here: simply create a new promise and resolve it after `time` seconds. Yes, seconds, the SI unit for time. We're writing code in the 21st century and floating point numbers have been around for long enough that there's no benefit in using milliseconds whatsoever other than being used to it. Even POSIX uses seconds for sleep timeouts, so that's what I'm doing.
With that out of the way, here's what a very basic `run()` method would look like, before adding further features:
```js
async run() {
while (true) {
// Next item to type out
let subject = this.children[0]
// Make a copy in case the original changes while we're typing it
let content = subject.cloneNode(true)
// Rotate the subject to the back
subject.remove()
this.append(subject)
await this.typeElement(this.shadowRoot, content)
await this.emptyElement(this.shadowRoot)
}
}
```
The important part here are the two functions at the end: These will take care of typing out our cloned element recursively, then deleting it again. The target will be a shadow DOM attached to the custom property in its `connectedCallback`.
## Optional Looping
So now we have an infinite loop, which might not be what the user wants. We need an option to turn the looping on or off. This is actually quite easy though! After all, the loop is just an asynchronous function.
```js
if (!this.loop) return
```
So we can simply return from it at the end of the iteration. What's more, this means that we can disable looping at any moment, and the type-writer will finish the current iteration and then stop.
To resume typing, we'd simply have to call the `run()` method again.
But this leads us to another problem: what if we call `run()` while the loop is already running? To avoid this, I simply added a state variable to the class, and for user convenience, exposed it as read-only:
```js
#running
get running() { return this.#running }
// ...
async run() {
while (true) {
if (this.running) return
this.#running = true
// The rest of the code
if (!this.loop) {
this.#running=false
return
}
}
}
```
Now users can query the read-only property `TypeWriter.running` and they call `run()` a second time, it will simply return immediately.
## Typing
The central part of the element is, of course, the part where it types out the contents. The biggest difficulty here is, that we don't deal with text, but a (potentially very deep) DOM subtree, and we need to type out characters one by one while reproducing the surrounding HTML structure; but we can't type out the HTML text because that's not visible to the user.
A simple solution to this problem is a set of functions, one that loops over the HTML elements recursively and one that handles the actual text content of these elements.
Starting with a function that handles HTML elements, the basic structure would look something like this:
```js
async typeElement(target, elem) {
for (let child of elem.childNodes) {
if ("data" in child) {
await this.typeText(target, child.textContent.replace(/\s+/g, ' '))
} else {
let copy = child.cloneNode(false)
target.append(copy)
await this.typeElement(copy, child)
}
}
}
```
The only two things worth pointing out here are that, before passing the content of a text node to `typeText`, all clusters of whitespace are replaced with a single space to mimic how HTML is rendered anyway. This would mean `<pre>` nodes don't work, so that's something to put on a list of future improvements. The other noteworthy thing is that `"data" in child` is probably not the best way to check for text nodes, but it does work.
For non-text nodes, the function inserts a shallow copy of the given node, appends it to the target and recursively calls itself.
Of course, all function calls need an `await` keyword, as both `typeText` and `typeElement` are asynchronous.
The `typeText` function is even simpler:
```js
async typeText(target, text) {
let node = document.createTextNode('')
target.append(node)
for (let char of text.split('')) {
node.appendData(char)
await sleep(this.type)
}
}
```
all it needs to do is iterate over the string character-by-character and append them to the target node, sleeping for a certain time in between each character.
## Deleting
Deleting of elements looks very similar to typing, except it has to be mirrored. For example, the `emptyText` method will have to remove characters from the back:
```js
async emptyText(target) {
while (target.data.length) {
target.data = target.data.slice(0, -1)
await sleep(this.back)
}
}
```
and the `emptyElement` function will have to iterate over child elements backwards, deleting them after they have been emptied:
```js
async emptyElement(target) {
let children = target.childNodes
while (children.length) {
let child = children[children.length-1]
if ("data" in child) {
await this.emptyText(child)
} else {
await this.emptyElement(child)
}
child.remove()
}
}
```
Other than this reversal, both methods look very similar to their typing counterparts; they recursively traverse the DOM subtree and act on each node.
## Events
With the visible parts of the element being implemented now, what's left is to add a usable JavaScript interface. JavaScript has several mechanisms of handling events, but the ones most widely used nowadays are events and promises, each with their own strengths and weaknesses.
Ideally, the API for the TypeWriter might look something like this:
- The Element has a series of Events describing state changes like "finished typing" or "started erasing"
- Each of these events dispatches an actual DOM Event that can be interacted with as usual.
- For convenience, for each of these events, the user can get a Promise that resolves the next time the event is emitted.
- For simpler cases, a user can embed event handling code in the HTML as with other events like `click` (via the `onclick` attribute) and many others.
Since all of these features will be using variants of the same event names, that's the best place to start:
```js
static eventNames = ["Typing", "Typed", "Erasing", "Erased", "Connected", "Resumed"]
```
Static Initialisation Blocks could be used to make looping over this static array and extending the class more readable; but since those aren't a thing yet, the code looks a bit more esoteric:
```js
static eventNames = ["Typing", "Typed", "Erasing", "Erased", "Connected", "Resumed"].map(name => {
// do stuff with "name"
return name
})
```
It's worth pointing out that this creates a new array containing the same elements as the first one, which is wasteful. Since this happens only once in a class definition, and the array isn't long, it won't effectively have any performance impact.
Inside this loop, two things will happen:
### Event Promises
For each of the known events, the class should have an attribute that returns a promise. Given the event name, this can be implemented quite easily with a bit of metaprogramming magic:
```js
Object.defineProperty(
TypeWriter.prototype, name.toLowerCase(),
{get() { return new Promise(resolve =>this.addEventListener(
name.toLowerCase(),
(event) => resolve(event.detail), {once: true})
)}}
)
```
Simply define a new property on the classes prototype with no setter and a getter that returns a new promise. It becomes a bit hard to read because the event-listener is added in the callback to the Promise constructor, but there's not really much magic going on other than the property definition.
### HTML Attributes
This feature has two parts, only the first of which happens inside the event-name loop:
#### Getting a Function
```js
Object.defineProperty(
TypeWriter.prototype,
`on${name}`,
{ get() {
return Function(this.getAttribute(`on${name}`))
}}
)
```
Once again, a new property is defined on the class, but this time there's another bit of meta-programming going on: the getter for the property accesses one of the element's attributes and turns the string into a function.
If, for example, a type-writer tag looks like this:
```html
<type-writer onTyped="console.log(event, this)"></type-writer>
```
then accessing the `onTyped` property on the object would return a function that's equivalent to the following:
```js
function() {
console.log(event, this)
}
```
#### Calling the Handler
The second part is to call this html-defined event handler, which can easily be done by adding an event listener in the object's constructor:
```js
constructor() {
super()
TypeWriter.eventNames.forEach(name => {
this.addEventListener(name.toLowerCase(), event => this[`on${name}`](event))
})
}
```
It simply loops over all the event names and adds a listener that makes use of the property to get a function and calls it. The `event` parameter passed to the event callback is just there as a hint that this is an event handler: it doesn't actually get uesd and can be turned into `_event` according to taste.
### Emitting Events
Now the last part is to actually emit some events. For this, I used a simple helper method to reduce boilerplate:
```js
emit(description, detail=undefined) {
this.dispatchEvent(new CustomEvent(description.toLowerCase(), {detail}))
}
```
There's not much to say about this; it's a very straight-forward helper method that can be called, for example, as `this.emit("typing", content)` right before the call to `typeElement` to signal that the type-writer is about to start typing some text.
-----
## Conclusion
So that's about it. There is, of course, a bunch more code dealing with the more boring technicalities, like skipping `<style>` tags when typing (as they are invisible anyway, and would appear as a random pause to the user if they were typed out), getters for delay-properties like `wait`, the time in seconds to wait after typing an element before starting to delete it, etc.
I hope this article was of some use, specially in illustrating how meta-programming can be used to shorten repetitive code like the adding of event-handlers, which would otherwise have been a long list of `get onTyped() {...}` and `addEventListener("typed", ...)` lines in the class definition, as well as how a set of recursive functions can be used to very easily traverse a DOM subtree.
I am aware that this example doesn't really target only one skill-level, as something like recursively traversing a tree is a lot more "basic" than dynamic property definitions and other meta-programming, but I hope that everyone can find at least one or two things they find helpful.
And last but not least, [here's the actual code](https://github.com/DarkWiiPlayer/components/blob/master/TypeWriter.js), but keep in mind that I changed and ommitted a few things to make the code easier to follow in the article.
What are your thoughts? Would you improve anything about this aproach? Have you ever found yourself using similar tools to solve a problem? Let me know in the comments and happy discussing! 💜