Dominic Myers writes about all sorts of stuff to do with HTML, CSS, JavaScript and a fair chunk of self-indulgent stuff. Thoughts and opinions are all his own, and nothing to do with any employer.
Last week, I got an itch, as I often do when checking the emails sent from Pinterest. There was one pin of an excellent animation of rings comprised of dots rotating around a centre, and, on each segment of the rotation, the dot would arrive back at its starting point - that's a terrible explanation. What I mean is, should the ring be comprised of 6 dots, then for every sixth of the whole rotation, the original dots location would match the location of the following dot; a ring consisting of 12 dots would be similar - in the time that the first ring took for the original dots location to be matched by the subsequent dots location, the original dots location would be matched by the subsequent dots location. I'm not explaining it well, sorry. It inspired me to replicate it in p5js, so that's what I did, though with some measure of trepidation, as I was sure it would require some level of trigonometry.
Now, I took GCSE maths, but trigonometry has always been something I was utterly terrified of. I blame it on slide rules that my Dad had and being completely unable to get it clear in my head—I've even taken courses on Khan Academy, but it's still something I can't seem to get straight in my head (geddit?).
Anyway, I figured out how to do the math to do it eventually (after significant research) but then got stuck trying to rotate the dots: interestingly, the number of dots increased by six on each ring, so 6, 12, 18, 24, 30, 36.
After pestering Oliwia, who was stuck in a meeting, I called my Dad and explained my situation. After he started talking about trigonometry for a few minutes, I remembered why it wouldn't stick. But, in the process of explaining it, I clocked that the rotation was based on an arbitrary figure within a loop and that by incrementing that figure, I could alter the placement of the dots:
After clocking that, I realised I didn't need to keep on incrementing the increment but could set it to zero once it had reached the step value, which was derived from:
this.step = p5.TWO_PI /this.number;
This means that each time the subsequent dot reached the position of the original dot, the animation could be reset! Instead of each ring rotating for the entire three hundred and sixty degrees, the inner ring rotated sixty degrees and returned to its original position. The next outer ring rotated twenty-one and two-thirds degrees, and so on. Neat eh? The original and my animation are here and here.
I was recently asked to help sort table columns, but there was a specific use case. The original data was a mixture of arrays and objects; some of the object keys represented the text to display in the table cell, some of the arrays represented child elements, and some object keys would become further text within cells, meaning that the elements at the top of the JSON tree would be repeated an arbitrary number of times. The use case was that if a column header representing a child element should be clicked, only the children would be sorted, and the parents would remain in the same order.
After some head-scratching, I concluded that the table had to effectively be chunked into blocks identified by the ancestors of the currently selected column. These chunks could then be sorted, and after discarding the idea of sorting them in place, I realised I'd need to empty the table and place them all back in order - thankfully, good HTML practice meant that I had a table header and a table body to play with (do you hate it as much as I when people neglect to use a thead element?).
That explanation seems relatively straightforward, but it wasn't—it took an age of thinking and ensuring things worked as expected.
I've lost count of the number of times I've needed to truncate text online, and I've tried all sorts of mechanisms, so when I came across Christian Heilmann'strimMiddle() function, I was happy as Larry.
There's a handy demo, and I have to say that getting it to respect changes to the inner text dynamically was an utter PITA! Have a play and see if it fits your requirements.
We're setting up an Archery Wiki on our site and using Firebase as our back end. Adding wikis is simple enough, as we can use a simple markdown editor, and displaying them is easy enough using the marvellous react-markdown package. However, navigation triggered a whole page refresh, which was less than ideal.
I thought there must be a way of intercepting anchor element clicks in React, but after searching for a while, I realised that intercepting clicks on all links was less than ideal anyway. Wouldn't it be better to listen to internal links, replace them with pseudo anchor elements, and trigger React's navigation instead?
Ages ago I wrote a simple clock, and recently I came across some lovely images by TransientCode, the combination of these factors led me to create a simple clock using the images; this is the result:
If you follow the source then you might notice that it makes noises each time the numerals change - each numeral has it's own sound - but this can become annoying! It won't make any sound until you click on the page though.
It was Voltaire who said that perfect is the enemy of good; it was me in many interviews who answered, "I'm a perfectionist", whenever anyone asked if I had a weakness. That's not strictly true; it's just something I read years ago about what to answer when asked that question... to be honest, it's good advice and a darn sight better than saying, "Benign dictatorship" when asked about my management style (I still think I would have been brilliant in that role too).
Anyway, it wasn’t precisely Voltaire’s aphorism that prompted me to write this; it was more along the lines of grokking that sometimes, being a purist can get in the way of making a useful thing. Let me explain a little more. I’ve been playing with Web Components for a long time, long before they became as popular as they seem to be now, and whenever I’ve created them, I’ve been conscious that the best place for them would be on npm. Once on npm, they can be imported using skypack or unpkg and used wherever without downloading and hosting them; they should just work (Indeed, whenever I demo them on codepen, that’s what I do to check the mechanism works).
To ensure that as many people find them helpful as possible, I try to make them as perfect as possible, anticipate where they might be used, and make them as flexible as possible. Even in the case of input elements, I try to make them form-associated (though that’s been a massive issue in the past—thanks to a dearth of information on making elements form-associated). This has stopped me from creating and using them in a more bespoke manner up until recently.
Recently, I’ve been involved as a subject-matter-expert (due to suffering a specific condition) with consulting and testing a research tool. I was provided with the underlying questionnaire to be used in that research. We were informed that a development team had been tasked with converting that questionnaire into an online tool which would record answers over days, weeks and months, and I thought that would be a fun way of filling a weekend – to try converting it myself. I’d also been reading about Beer CSS, which aims to translate a modern UI into an HTML semantic standard, which also sounded like a fun tool to play with.
As I started developing the application, I noticed that there would be many repeated code blocks. Each daily question was repeated six times, and the weekly question was repeated fourteen times. Once I started coding it, I noticed that the only difference between the questions was the specific language used and the options. Each question had a radio button to click for the value appropriate to the respondent. This was a perfect place to use a slot within a web component!
I started with the weekly question and copied the markup I’d already implemented:
You’ll no doubt appreciate the sheer amount of copying and pasting to get fourteen of these on the page simultaneously, but this translated into the following template literal within the component:
It was much neater, especially as the constructor had the values hard coded:
this.labels = [
"None",
"A little bit",
"Moderately",
"Quite a bit",
"Extreme",
];
The keen-eyed amongst you will notice that we have several private values, namely #value, #locked, and #disabled. That’s not to say we don’t expose these values; we can set the value, locked, and disabled attributes, which will update the private values using getters and setters. Further, as we’ve defined the component as being form-associated, when we set the attribute from inside the element, the containing form can be notified of the change by dispatching a change event.
This is the complete code (as always, I’m more than happy to have input into how it might be improved):
Except for the hardcoded label values, this is an inherently reusable component. Still, the next element—the daily question elements—was far more custom, not least because it was my first attempt at using a component as a table row. This is the markup I needed to produce:
<tris="wc-easy-question-row"name="Domain-1-1"value="2"><thclass="weight-normal vertical-align-bottom">
1. Some <spanclass="bold">strong</span> and important question?
</th><tdclass="center-align"><labelclass="radio"title="Not limied at all"><inputtype="radio"name="Domain-1-1"value="0"class="middle center"><span></span></label></td><tdclass="center-align"><labelclass="radio"title="A little limited"><inputtype="radio"name="Domain-1-1"value="1"class="middle center"><span></span></label></td><tdclass="center-align"><labelclass="radio"title="Moderately limited"><inputtype="radio"name="Domain-1-1"value="2"class="middle center"checked=""><span></span></label></td><tdclass="center-align"><labelclass="radio"title="Very limited"><inputtype="radio"name="Domain-1-1"value="3"class="middle center"><span></span></label></td><tdclass="center-align"><labelclass="radio"title="Totally limited / unable to do"><inputtype="radio"name="Domain-1-1"value="4"class="middle center"><span></span></label></td></tr>
As you can see, we’re extending the HTMLTableRowElement and making it form-associated. This is the whole implementation:
import { v4 as uuidv4 } from "https://cdn.skypack.dev/uuid";
class WCEasyQRow extends HTMLTableRowElement {
#value =null;
#disabled =false;
static get observedAttributes() {
return ["value", "name", "disabled"];
}
static formAssociated =true;
constructor() {
super();
this.labels = [
"Not limied at all",
"A little limited",
"Moderately limited",
"Very limited",
"Totally limited / unable to do",
];
this.name = uuidv4();
}
render() {
this.removeEventListener("change", this.handleChange);
const tds =this.querySelectorAll("td");
for (const td of tds) {
td.remove();
}
this.insertAdjacentHTML("beforeend", this.html);
this.addEventListener("change", this.handleChange);
}
get html() {
returnthis.labels
.map(
(label, i) =>`<td class="center-align"><label class="radio"
title="${label}"><input type="radio"
name="${this.name}"
value="${i}"class="middle center"
${this.#disabled ?"disabled":""}
${this.#value === i ?"checked":""} /><span></span></label></td>`,
)
.join("");
}
set name(value) {
this.setAttribute("name", this.name);
}
get name() {
returnthis.hasAttribute("name") &&this.getAttribute("name") !==null?this.getAttribute("name")
:this.name;
}
set disabled(value) {}
get disabled() {
this.#disabled =this.hasAttribute("disabled");
returnthis.#disabled;
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
if (name ==="value") {
this.#value = newValue ?Number(newValue) :null;
this.render();
}
if (name ==="disabled") {
this.#disabled =this.hasAttribute("disabled");
this.render();
}
}
}
connectedCallback() {
this.render();
}
set value(value) {
this.#value =Number(value);
this.setAttribute("value", this.#value);
}
get value() {
returnthis.hasAttribute("value") &&this.getAttribute("value") !==null?Number(this.getAttribute("value"))
:null;
}
handleChange(event) {
this.value =Number(event.target.value);
if (this.#value !==Number(event.target.value)) {
this.dispatchEvent(new Event("change"));
}
}
}
customElements.define("wc-easy-question-row", WCEasyQRow, {
extends:"tr",
});
Creating these components didn’t save me much time over copying and pasting the relevant markup and changing the text and names of the radio inputs. Still, it did mean that should I discover an issue when creating the inputs, I only had to address the problem in one file for all the relevant inputs to be fixed simultaneously. And more importantly, it meant the classes would act as templates for future implementations. I’m not adding them to npm as they are only helpful to me, but I can think about abstracting the classes in the future so that they might be more flexible. The second example – the extended table row element – is unsuitable for reuse in any current project I’m working on, but it might be in the future.
Interestingly, I did have a minor issue with them, perhaps due to the sheer number of moving parts. I originally had a render function that ran once when the component mounted. I then did all sorts of interesting internal DOM manipulation, but every so often, the elements would not reflect the changes, so every time something needs to change in the DOM, I re-render it, and things don’t mess up now.
My primary concern is the hard coding of the values, but I’ve read that passing complicated object data in an attribute is considered a bad thing, so I’m not sure how best to address that other than using slots. Sure, I could do complicated things like using JSON strings or Base64 encoded data, but that seems to be getting away from the spirit of web components. I’ve read about passing data using properties. Still, for this example, at least, the only hard-coded values are the labels for the inputs, and they stop the same, so I might as well leave them like that; making them properties might increase the utility of the classes and encourage internalisation and reuse.
Perhaps making them proper web components suitable for use by others and thus worthy of popping into npm might be a job when I have a little time. The inclusion of the specific CSS for the first component might also be an attribute, and this would increase that component's utility.
But, going back to Voltaire, as you’ll doubtless clock from this rambling, while I embraced the less-than-perfect (no utility outside the specific project and no aim to upload to npm) in creating these two components, creating them meant that I could see how they might be made more perfect; I particularly enjoyed the whole locking mechanism, and this is something that I’d like to explore more, though with an appreciation that this might not be required elsewhere – perhaps the locking functionality needs to be made optional before I abstract the class further. You’ll also notice the inclusion of uuidv4 so that, should I forget to add the name when adding the component, the radio buttons will all share the same name, preventing more than one radio button from being checked at a time.
In this instance, I chose Practicality over Purity, and that's fine in my books... for now.
Saturday, 15 July 2023
I’ve recently been updating a website and trying to implement a data of birth field; what do you think is the best input type for such a field?
<inputtype="date"id="dob"name="dob"/>
I thought a date type input would be best, but then I tried entering the date of birth of a lady joining the club on my phone, and I gave up! I asked her to email it to me as it was getting embarrassing scrolling through all the months to the 1970s – see, she wasn’t even as old as me, and I thought about how annoying it is to have to scroll to enter my date of birth!
At least on Android, you must swipe through each month in each year, going back in time to that halcyon time when you were first spawned. 50 years of 12 months are 600 swipes through your history, and let me tell you, that gets depressing fast!
Anyway, I thought about how it’s managed elsewhere and found that the UK Government’s implementation (also used by the NHS) and the GOV.UK Design System has a lovely mechanism for date inputs with three separate fields (one for the day, one for the month and one for the year). It’s located here, but the page tends to break. The NHSs version doesn’t break, though – and they acknowledge its ancestry.
As a fan of web components, I thought I’d implement the same thing, add it to our website, and allow others to take advantage of it as well. I built-in date validation using the native date object rather than Moment.js or date-fns, as I’ve played before with using dropdown fields to enter the values in a Vue component in the dim and distant past – that was fun though as the dropdown values would only allow valid options to be selected. In that component, you could only choose leap years if you had selected the 29th of February, for instance.
What I was particularly pleased about was the ability to add native form validation, which is something I’ve never tried before but was quite easy to implement. I’ve utilised significant manual testing and asked a mate to do some Selenium testing – but unit testing within web components seems to be something of a dark art, especially if not using a framework to create the component – and why would you use a framework if you’re avoiding frameworks and writing native web components?
There’s a CODEPEN to play with here, and it’s on npm so that you can play with it too – if you spot anything, please let me know; hey, tell me your thoughts anyway! I’ve used it within a React application, and it also seems to work well there.
HTML is brilliant; it's an ever-growing standard; CSS is also pretty amazing! I say this because I was bored over the evenings this week and brushed off an old thing I started years ago to help me with the inability to style range inputs. You can do some cool styling on range inputs now, but when I created my initial component, it wasn't easy and involved many vendor prefixes, with some target browsers unable to handle even those. So I decided to write a component to get our designer's desired effect.
But I was bored this week, so I decided to revisit it; you can see the result on JSFiddle.
It was a lot of fun doing this, I'm not sure if I'll ever need it again, but it was a valuable learning experience. Asking for help from a mate meant that she could echo back something I've said to her many times before: "Have you tried flex?", so embarrassing, but it just goes to show! One issue was that I was sticking to the original CSS too much and didn't have to cope with variable-sized wc-sliders. You see, the little arrows behind the slider element moved depending on the number of elements in the slider.
The Drag-and-Drop mechanism took the most time and - as a result of checking my workings - I clocked I was using the getters for range, colourRange and deselectedRange far too much. I moved these to Private class features and only calculate them on render(). Thanks to the dictates of working with a designer, I also spent a fair bit of effort working with colours and gradients - a bit of a ballache, TBH, but I've got the functions now so that I can use them again in the future!
I do like the invertColor(hex, bw) though. That's tremendous fun and might be useful should you check what colour you need to set text atop different coloured backgrounds. Looking at it again now, though, I think the none bw might be a little borked; I'll fix it as and when I've time.
It's a bit CSS-heavy, but no worse for that, TBH; if you can do something with CSS, then it's better than firing up JS - let the browser do the work!
From the README:
Dragging the slider element will alter the value surfaced by the component from its value attribute (should the number in which the drop ends be greater than or equal to the constrain-min or less than or equal to the constrain-max)
Clicking on the numbers shown at the top of each step will alter the value surfaced by the component from its value attribute (should the number be greater than or equal to the constrain-min or less than or equal to the constrain-max).
Clicking on the triangles on either side of the slider will alter the value surfaced by the component from its value attribute by +/- 1 (should the number resulting from the click be greater than or equal to the constrain-min or less than or equal to the constrain-max).
As you can doubtless see, there are three ways of changing the value and - given the work I put into checking the bugger, no way for an invalid value to be produced - though you might not like the value. Having said that, if you manage to break it, please let me know, so I can fix it!
It has some default attributes so that you can test it for your use case.
Again, from the README:
The range of numbers, inclusive of min and max, should not be too large - tests have found that the ranges should not have more than 15 numbers, but YMMV.
One thing to consider in the future is whether or not to allow for a comma-separated list of hex values so that designers can adequately define what colours should appear on the wc-slider.