Categories
News Personal

New decade, new theme

Reading Time: 2 minutes

It has been almost a decade since this blog last saw a redesign.

This blog’s theme 2011 – 2020. RIP!

In these 9 years, my life changed dramatically. I joined and left W3C, joined the CSS WG, went to MIT for a PhD, published a book, got married, had a baby, among other things. I designed dozens of websites for dozens of projects, but this theme remained constant, with probably a hasty tweak here and there but nothing more than that. Even its mobile version was a few quick media queries to make it palatable on mobile.

Categories
Rants

Today’s Javascript, from an outsider’s perspective

Reading Time: 3 minutes

Today I tried to help a friend who is a great computer scientist, but not a JS person use a JS module he found on Github. Since for the past 6 years my day job is doing usability research & teaching at MIT, I couldn’t help but cringe at the slog that this was. Lo and behold, a pile of unnecessary error conditions, cryptic errors, and lack of proper feedback. And I don’t feel I did a good job communicating the frustration he went through in the one hour or so until he gave up.

Categories
Articles CSS WG Original Releases

LCH colors in CSS: what, why, and how?

Reading Time: 8 minutes

I was always interested in color science. In 2014, I gave a talk about CSS Color 4 at various conferences around the world called “The Chroma Zone”. Even before that, in 2009, I wrote a color picker that used a hidden Java applet to support ICC color profiles to do CMYK properly, a first on the Web at the time (to my knowledge). I never released it, but it sparked this angry rant.

Color is also how I originally met my now husband, Chris Lilley: In my first CSS WG meeting in 2012, he approached me to ask a question about CSS and Greek, and once he introduced himself I said “You’re Chris Lilley, the color expert?!? I have questions for you!”. I later discovered that he had done even more cool things (he was a co-author of PNG and started SVG 🤯), but at the time, I only knew of him as “the W3C color expert”, that’s how much into color I was (I got my color questions answered much later, in 2015 that we actually got together).

My interest in color science was renewed in 2019, after I became co-editor of CSS Color 5, with the goal of fleshing out my color modification proposal, which aims to allow arbitrary tweaking of color channels to create color variations, and combine it with Una’s color modification proposal. LCH colors in CSS is something I’m very excited about, and I strongly believe designers would be outraged we don’t have them yet if they knew more about them.

Categories
Articles

Issue closing stats for any repo

Reading Time: 7 minutes

tl;dr: If you just want to quickly get stats for a repo, you can find the app here. The rest of this post explains how it’s built with Mavo HTML, CSS, and 0 lines of JS. Or, if you’d prefer, you can just View Source — it’s all there!

The finished app we’re going to make, find it at https://leaverou.github.io/issue-closing

One of the cool things about Mavo is how it enables one to quickly build apps that utilize the Github API. At some point I wanted to compute stats about how quickly (or rather, slowly…) Github issues are closed in the Mavo repo. And what better way to build this than a Mavo app? It was fairly easy to build a prototype for that.

Categories
Uncategorized

Utility: Convert SVG path to all-relative or all-absolute commands

Reading Time: 3 minutes

I like hand-editing my SVGs. Often I will create an initial version in Illustrator, and then export and continue with hand editing. Not only is it a bit of a meditative experience and it satisfies my obsessive-compulsive tendencies to clean up the code, it has actual practical benefits when you need to make certain changes or introduce animation. Some things are easier to do in a GUI, and others are easier to do in code, and I like having the flexibility to pick which one fits my use case best.

However, there was always a thing that was a PITA: modifying paths. Usually if I need anything more complicated than just moving them, I’d do it in Illustrator, but even moving them can be painful if they are not all relative (and no, I don’t like introducing pointless transforms for things that should really be in the d attribute).

For example, this was today’s result of trying to move an exported “a” glyph from Raleway Bold by modifying its first M command:

Trying to move a path by changing its first M command when not all of its commands are relative.

This happened because even though most commands were exported as relative, several were not and I had not noticed. I have no idea why some commands were exported as absolute, it seems kind of random.

When all commands are relative, moving a path is as simple as manipulating its initial M command and the rest just adapts, because that’s the whole point of relative commands. Same with manipulating every other part of the path, the rest of it just adapts. It’s beautiful. I honestly have no idea why anybody would favor absolute commands. And yet, googling “convert SVG path to relative” yields one result, whereas there are plenty of results about converting paths to absolute. No idea why that’s even desirable, ever (?).

I remembered I had come across that result before. Thankfully, there’s also a fiddle to go with it, which I had used in the past to convert my path. I love it, it uses this library called Snap.svg which supports converting paths to relative as a just-add-water utility method. However, that fiddle is a quick demo to answer a StackOverflow question, so the UI is not super pleasant to use (there is no UI: you just manipulate the path in the SVG and wait for the fiddle to run). This time around, I needed to convert multiple paths, so I needed a more efficient UI.

So I created this demo which is also based on Snap.svg, but has a slightly more efficient UI. You just paste your path in a textarea and it both displays it and instantly converts it to all-relative and all-absolute paths (also using Snap.svg). It also displays both your original path and the two converted ones, so you can make sure they still look the same. It even follows a pending-delete pattern so you can just focus on the output textarea and hit Cmd-C in one fell swoop.

I wasn’t sure about posting this or just tweeting it (it literally took less than 30 minutes — including this blog post — and I tend to only post small things like that on my twitter), but I thought it might be useful to others googling the same thing, so I may as well post it here for posterity. Enjoy!

Categories
Articles

ReferenceError: x is not defined?

Reading Time: 2 minutes

Today for a bit of code I was writing, I needed to be able to distinguish “x is not defined” ReferenceErrors from any other error within a try...catch block and handle them differently.

Now I know what you’re thinking. Trying to figure out exactly what kind of error you have programmatically is a well-known fool’s errand. If you express a desire to engage in such a risky endeavor, any JS veteran in sight will shake their head in remembrance of their early days, but have the wisdom to refrain from trying to convince you otherwise; they know that failing will teach you what it taught them when they were young and foolish enough to attempt such a thing.

Despite writing JS for 13 years, today I was feeling adventurous. “But what if, just this once, I could get it to work? It’s a pretty standard error message! What if I tested in so many browsers that I would be confident I’ve covered all cases?”

I made a simple page on my server that just prints out the error message written in a way that would maximize older browser coverage. Armed with that, I started visiting every browser in my BrowserStack account. Here are my findings for anyone interested:

  • Chrome (all versions, including mobile): x is not defined
  • Firefox (all versions, including mobile): x is not defined
  • Safari 4-12 : Can't find variable: x
  • Edge (16 – 18): 'x' is not defined
  • Edge 15: 'x' is undefined
  • IE6-11 and Windows Phone IE: 'x' is undefined
  • UC Browser (all versions): x is not defined
  • Samsung browser (all versions): x is not defined
  • Opera Mini and Pre-Chromium Opera: Undefined variable: x

Even if you, dear reader, are wise enough to never try and detect this error, I thought you may find the variety (or lack thereof) above interesting.

I also did a little bit of testing with a different UI language (I picked Greek), but it didn’t seem to localize the error messages. If you’re using a different UI language, please open the page above and if the message is not in English, let me know!

In the end, I decided to go ahead with it, and time will tell if it was foolish to do so. For anyone wishing to also dabble in such dangerous waters, this was my checking code:

if (e instanceof ReferenceError 
    && /is (not |un)defined$|^(Can't find|Undefined) variable/.test(e.message)) {
    // do stuff
}

Found any cases I missed? Or perhaps you found a different ReferenceError that would erroneously match the regex above? Let me know in the comments!

One thing that’s important to note is that even if the code above is bulletproof for today’s browser landscape, the more developers that do things like this, the harder it is for browser makers to improve these error messages. However, until there’s a better way to do this, pointing fingers at developers for wanting to do perfectly reasonable things, is not the solution. This is why HTTP has status codes, so we don’t have to string match on the text. Imagine having to string match “Not Found” to figure out if a request was found or not! Similarly, many other technologies have error codes, so that different types of errors can be distinguished without resulting to flimsy string matching. I’m hoping that one day JS will also have a better way to distinguish errors more precisely than the general error categories of today, and we’ll look back to posts like this with a nostalgic smile, being so glad we don’t have to do crap like this ever again.

Categories
Articles Releases

Refresh CSS Bookmarklet v2

Reading Time: 2 minutesAlmost 11 years ago, Paul Irish posted this brilliant bookmarklet to refresh all stylesheets on the current page. Despite the amount of tools, plugins, servers to live reload that have been released over the years, I’ve always kept coming back to it. It’s incredibly elegant in its simplicity. It works everywhere: locally or remotely, on any domain and protocol. No need to set up anything, no need to alter my process in any way, no need to use a specific local server or tool. It quietly just accepts your preferences and workflow instead of trying to change them. Sure, it doesn’t automatically detect changes and reload, but in most cases, I don’t want it to.

I’ve been using this almost daily for a decade and there’s always been one thing that bothered me: It doesn’t work with iframes. If the stylesheet you’re editing is inside an iframe, tough luck. If you can open the frame in a new tab, that works, but often that’s nontrivial (e.g. the frame is dynamically generated). After dealing with this issue today once more, I thought “this is just a few lines of JS, why not fix it?”.

The first step was to get Paul’s code in a readable format, since the bookmarklet is heavily minified:

(function() {
	var links = document.getElementsByTagName('link');
	for (var i = 0; i < links.length; i++) {
		var link = links[i];
		if (link.rel.toLowerCase().match(/stylesheet/) && link.href) {
			var href = link.href.replace(/(&|%5C?)forceReload=\d+/, '');
			link.href = href + (href.match(/\?/) ? '&' : '?') + 'forceReload=' + (new Date().valueOf())
		}
	}
})()

Once I did that, it became obvious to me that this could be shortened a lot; the last 10 years have been wonderful for JS evolution!

(()=>{
	for (let link of Array.from(document.querySelectorAll("link[rel=stylesheet][href]"))) {
		var href = new URL(link.href, location);
		href.searchParams.set("forceReload", Date.now());
		link.href = href;
	}
})()

Sure, this reduces browser support a bit (most notably it excludes IE11), but since this is a local development tool, that’s not such a big problem.

Now, let’s extend this to support iframes as well:

{
	let $$ = (selector, root = document) => Array.from(root.querySelectorAll(selector));
	
	let refresh = (document) => {
		for (let link of $$("link[rel=stylesheet][href]", document)) {
			let href = new URL(link.href);
			href.searchParams.set("forceReload", Date.now());
			link.href = href;
		}

		for (let iframe of $$("iframe", document)) {
			iframe.contentDocument && refresh(iframe.contentDocument);
		}
	}

	refresh();
}

That’s it! Do keep in mind that this will not work with cross-origin iframes, but then again, you probably don’t expect it to in that case.

Now all we need to do to turn it into a bookmarklet is to prepend it with javascript: and minify the code. Here you go:

Refresh CSS

Hope this is useful to someone else as well 🙂
Any improvements are always welcome!

Credits

  • Paul Irish, for the original bookmarklet
  • MaurĂ­cio Kishi, for making the iframe traversal recursive (comment)
Categories
Original Tips

Easy Dynamic Regular Expressions with Tagged Template Literals and Proxies

Reading Time: 3 minutesIf you use regular expressions a lot, you probably also create them from existing strings that you first need to escape in case they contain special characters that need to be matched literally, like $ or +. Usually, a helper function is defined (hopefully this will soon change as RegExp.escape() is coming!) that basically looks like this:

var escapeRegExp = s => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");

and then regexps are created by escaping the static strings and concatenating them with the rest of the regex like this:

var regex = RegExp(escapeRegExp(start) + '([\\S\\s]+?)' + escapeRegExp(end), "gi")

or, with ES6 template literals, like this:

var regex = RegExp(`${escapeRegExp(start)}([\\S\\s]+?)${escapeRegExp(end)}`, "gi")

(In case you were wondering, this regex is taken directly from the Mavo source code)

Isn’t this horribly verbose? What if we could define a regex with just a template literal (`${start}([\\S\\s]+?)${end}` for the regex above) and it just worked? Well, it turns out we can! If you haven’t seen tagged template literals before, I suggest you click that MDN link and read up. Basically, you can prepend an ES6 template literal with a reference to a function and the function accepts the static parts of the string and the dynamic parts separately, allowing you to operate on them!

Categories
Personal

Never forget type=”button” on generated buttons!

Reading Time: 2 minutes I just dealt with one of the weirdest bugs and thought you may find it amusing too.

In one of my slides for my upcoming talk “Even More CSS Secrets”, I had a Mavo app on a <form>, and the app included a collection to quickly create a UI to manage pairs of values for something I wanted to calculate in one of my live demos. A Mavo collection is a repeatable HTML element with affordances to add items, delete items, move items etc. Many of these affordances are implemented via <button> elements generated by Mavo.

Normally, hitting Enter inside a text field within a collection adds a new item, as one would expect. However, I noticed that when I hit Enter inside any item, not only no item was added, but an item was being deleted, with the usual “Item deleted [Undo]” UI and everything!

At first I thought it was a bug with the part of Mavo code that adds items on Enter and deletes empty items on backspace, so I commented that out. Nope, still happening. I was already very puzzled, since I couldn’t remember any other part of the codebase that deletes items in response to keyboard events.

So, I added breakpoints on the delete(item) method of Mavo.Collection to inspect the call stack and see how execution got there. Turned out, it got there via a normal …click event on the actual delete button! What fresh hell was this? I never clicked any delete button!

And then it dawned on me: <button> elements with no type attribute set are submit buttons by default! Quote from spec: The missing value default and invalid value default are the Submit Button state.. This makes no difference in most cases, UNLESS you’re inside a form. The delete button of the first item had been turned into the de facto default submit button just because it was the first button in that form and it had no type!

I also remembered that regardless of how you submit a form (e.g. by hitting Enter on a single-line text field) it also fires a click event on the default submit button, because people often listen to that instead of the form’s submit event. Ironically, I was cancelling the form’s submit event in my code, but it still generated that fake click event, making it even harder to track down as no form submission was actually happening.

The solution was of course to go through every part of the Mavo code that generates buttons and add type=”button” to them. I would recommend this to everyone who is writing libraries that will operate in unfamiliar HTML code. Most of the time a type-less <button> will work just fine, but when it doesn’t, things get really weird.

Categories
Original Tips

Responsive tables, revisited

Reading Time: 2 minutes
Screenshot showing a table with 3 rows turning into 3 sets of key-value pairs

Many people have explored responsive tables. The usual idea is turning the table into key-value pairs so that cells become rows and there are only 2 columns total, which fit in any screen. However, this means table headers need to now be repeated for every row. The current ways to do that are: