Showing posts with label Thoughts. Show all posts
Showing posts with label Thoughts. Show all posts

Monday, 26 August 2024

Practicality over Purity

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:

<article class="large-padding">
  <i>lock_open</i>
  <p>
    <slot></slot>
  </p>
  <div class="grid d-grid-10">
    <div class="col s10 m5 l2">
      <label class="radio">
        <input type="radio"
               name="Domain-2-1"
               value="0">
        <span class="bold">None</span>
      </label>
    </div>
    <div class="col s10 m5 l2">
      <label class="radio">
        <input type="radio"
               name="Domain-2-1"
               value="1">
        <span class="bold">A little bit</span>
      </label>
    </div>
    <div class="col s10 m5 l2">
      <label class="radio">
        <input type="radio"
               name="Domain-2-1"
               value="2">
        <span class="bold">Moderately</span>
      </label>
    </div>
    <div class="col s10 m5 l2">
      <label class="radio">
        <input type="radio"
               name="Domain-2-1"
               value="3">
        <span class="bold">Quite a bit</span>
      </label>
    </div>
    <div class="col s10 m5 l2">
      <label class="radio">
        <input type="radio"
               name="Domain-2-1"
               value="4">
        <span class="bold">Extreme</span>
      </label>
    </div>
  </div>
</article>

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:

<article class="large-padding">
  <i${this.#disabled ? ' class="tertiary-text"' : ""}>
    ${this.#locked || this.#disabled ? "lock" : "lock_open"}
  </i>
  <p><slot></slot></p>
  <div class="grid d-grid-10">
    ${this.labels.map((label, i) => `
      <div class="col s10 m5 l2">
        <label class="radio">
          <input type="radio"
                 name="${this.name}"
                 value="${i}"
                 ${this.#value === i ? " checked" : ""}
                 ${this.#locked || this.#disabled ? " disabled" : ""} />
          <span class="bold">${label}</span>
        </label>
      </div>
    `).join("")}
  </div>
</article>

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):

import { v4 as uuidv4 } from "https://cdn.skypack.dev/uuid";

class WCEasyQ extends HTMLElement {
  #value = null;
  #locked;
  #disabled;

  static get observedAttributes() {
    return ["value", "name", "locked", "disabled"];
  }
  static formAssociated = true;

  constructor() {
    super();
    this.labels = [
      "None",
      "A little bit",
      "Moderately",
      "Quite a bit",
      "Extreme",
    ];
    this.internals = this.attachInternals();
    this.shadow = this.attachShadow({
      mode: "closed",
      delegatesFocus: true,
    });
    this.name = uuidv4();
  }

  get css() {
    return `
      <style>
        @import url("https://cdn.jsdelivr.net/npm/beercss@3.6.0/dist/cdn/beer.min.css");
        .d-grid-10 {
          margin-block-start: 1rem;
          ---gap: 1rem;
          display: grid;
          grid-template-columns: repeat(
            10,
            calc(10% - var(---gap) + (var(---gap) / 10))
          );
          gap: var(---gap);
        }
        article {
          & i {
            &.tertiary-text {
              cursor: not-allowed !important;
            }
            &:first-child {
              position: absolute;
              top: 10px;
              right: 10px;
              cursor: pointer;
            }
          }
        }
      </style>
    `;
  }

  get html() {
    return `
      <article class="large-padding">
        <i${this.#disabled ? ' class="tertiary-text"' : ""}>
          ${this.#locked || this.#disabled ? "lock" : "lock_open"}
        </i>
        <p><slot></slot></p>
        <div class="grid d-grid-10">
          ${this.labels.map((label, i) => `
            <div class="col s10 m5 l2">
              <label class="radio">
                <input type="radio"
                       name="${this.name}"
                       value="${i}"
                       ${this.#value === i ? " checked" : ""}
                       ${this.#locked || this.#disabled ? " disabled" : ""} />
                <span class="bold">${label}</span>
              </label>
            </div>
          `).join("")}
        </div>
      </article>
    `;
  }

  set value(value) {
    if (value !== null) {
      this.setAttribute("value", Number(value));
      this.#value = Number(value);
    } else {
      this.removeAttribute("value");
      this.#value = null;
    }
    this.internals.setFormValue(this.#value);
  }

  get value() {
    this.#value =
      this.hasAttribute("value") && this.getAttribute("value") !== null
        ? Number(this.getAttribute("value"))
        : null;
    return this.#value;
  }

  set name(value) {
    this.setAttribute("name", this.name);
  }

  get name() {
    return this.hasAttribute("name") && this.getAttribute("name") !== null
      ? this.getAttribute("name")
      : this.name;
  }

  set locked(value) {
    this.#locked = value;
    this.render();
  }

  get locked() {
    this.#locked = this.hasAttribute("locked");
    return this.#locked;
  }

  set disabled(value) {}

  get disabled() {
    this.#disabled = this.hasAttribute("disabled");
    return this.#disabled;
  }

  handleDisabled(value) {
    this.render();
  }

  render() {
    if (this.shadow) {
      this.shadow.removeEventListener("change", this.handleChange);
    }
    if (this.icon) {
      this.icon.removeEventListener("click", this.handleClick);
    }
    this.shadow.innerHTML = `${this.css}${this.html}`;
    this.icon = this.shadow.querySelector("i");
    this.inputs = this.shadow.querySelectorAll("input");
    this.shadow.addEventListener("change", this.handleChange.bind(this));
    this.icon.addEventListener("click", this.handleClick.bind(this));
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue !== newValue) {
      if (name === "locked") {
        this.#locked = this.hasAttribute("locked");
      }
      if (name === "disabled") {
        this.#disabled = this.hasAttribute("disabled");
      }
      if (name === "value") {
        this.value = newValue ? Number(newValue) : null;
      }
      this.render();
    }
  }

  handleClick(event) {
    event.preventDefault();
    this.#locked = !this.#locked;
    this.render();
  }

  handleChange(event) {
    this.value = Number(event.target.value);
    this.dispatchEvent(
      new CustomEvent("change", {
        bubbles: true,
        composed: true,
      }),
    );
  }

  connectedCallback() {
    this.render();
  }
}

customElements.define("wc-easy-question", WCEasyQ);

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:

<tr is="wc-easy-question-row"
    name="Domain-1-1"
    value="2">
  <th class="weight-normal vertical-align-bottom">
    1. Some <span class="bold">strong</span> and important question?
  </th>
  <td class="center-align">
    <label class="radio" title="Not limied at all">
      <input type="radio"
             name="Domain-1-1"
             value="0"
             class="middle center">
      <span></span>
    </label>
  </td>
  <td class="center-align">
    <label class="radio" title="A little limited">
      <input type="radio"
             name="Domain-1-1"
             value="1"
             class="middle center">
      <span></span>
    </label>
  </td>
  <td class="center-align">
    <label class="radio" title="Moderately limited">
      <input type="radio"
             name="Domain-1-1"
             value="2"
             class="middle center"
             checked="">
      <span></span>
    </label>
  </td>
  <td class="center-align">
    <label class="radio" title="Very limited">
      <input type="radio"
             name="Domain-1-1"
             value="3"
             class="middle center">
      <span></span>
    </label>
  </td>
  <td class="center-align">
    <label class="radio" title="Totally limited / unable to do">
      <input type="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() {
    return this.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() {
    return this.hasAttribute("name") && this.getAttribute("name") !== null
      ? this.getAttribute("name")
      : this.name;
  }

  set disabled(value) {}

  get disabled() {
    this.#disabled = this.hasAttribute("disabled");
    return this.#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() {
    return this.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.

Friday, 27 January 2023

Smart meter? Snitch meter!

Last night I was woken by a thought - well, that's not correct; I was mainly woken by pins and needles caused by the dog sleeping on my feet! As I wriggled my toes and tried to get the blood flowing again (while ignoring the growls from the dog), I was struck by the thought of energy prices and how they were likely to fall. Wholesale energy prices are falling - some would say plummetting - so I'm guessing that OFGEM will lower the cap sooner rather than later.

Alongside the appreciation that the currently dire situation is likely to change, I remembered something mentioned when I was studying. An old professor asked us to consider how much of our utility bill comprised the cost of the mechanisms employed by calculating our bill. He mentioned that once telephone lines are installed, the outgoings for telephony companies are maintenance and installation of new lines and staff. As such, everything but a small portion of our bills was free and clear profit after paying off the initial investment in infrastructure. He suggested that roughly half of the phone bill was to cover the generation of the bills themselves: the recording of call duration and destination and the postage of those bills to us. Of course, I'm guessing that's changed in these times of online accounts, with that portion of the bill significantly reduced. Have these efficiencies made their way to us as decreasing bills?

I'm reminded of the boon that social media is to the intelligence services - instead of dedicating personnel to track our movements; we do it ourselves.

This reduction was probably the same for the other utilities, such as gas or electricity (let's pop them under the umbrella of energy utilities). I am trying to remember the last time we had any of our meters read - but it was certainly a fair few years ago - thus, the staffing costs have been reduced. This reduction in the number of meter readers has been thanks to smart meters and consumers providing their own readings.

But that's odd, isn't it? Instead of someone coming each quarter to check how much energy has been used in a property and a bill generated from that figure, we now provide that data monthly - or more frequently when we have a smart meter. Now we can tell how much energy we've used and be charged the going rate for that energy at that particular time. December is cold, and the energy price rises; we'll pay more. January is even colder, but energy prices have dropped - do we pay less...? The delay in OFGEM dropping their cap means that we don't - though the utility companies are paying less for the energy they provide.

So what to do? I'm pondering how well I should report my energy consumption. If the cap is high - is it better to report reduced usage as a consumer that provides their readings...? When the cap is lower, I can give increased figures to get me back up to the actual amount. Presumably, that would be far better than relying on the estimated figures used by energy companies - at least for me as a consumer. It does smack of being a gamble, though; who is to say when prices will fall?

Thursday, 6 October 2022

Democide

As reported in The Guardian in 2019, the Institute of Public Policy Research (IPPR) said more than "130,000 deaths in the UK since 2012 could have been prevented if improvements in public health policy had not stalled as a direct result of austerity cuts". More conservatively, the British Medical Journal (BMJ) said that in 2017, austerity was linked to 120,000 extra deaths in England (just in England, not Scotland, Wales or Northern Ireland). In 2018, the Office for National Statistics (ONS) showed a fall in life expectancy for poorer socioeconomic groups and those living in more impoverished areas.

So, it's not like we've not known the Tories have had an appalling impact on our population, but, on top of these utterly shocking figures, we're now presented with yet more. The Independent reported today (05/10/2022), "The UK government's economic policies are 'likely' to have caused a 'great many more deaths' than the Covid Pandemic, an academic has claimed". The Independent used an article from the Journal of Epidemiology and Community Health (JECH) titled "Bearing the burden of austerity: how do changing mortality rates in the UK compare between men and women?" by Walsh, Dundas, McCartney, Gibson and Seaman. Walsh et al. looked at previous statistics, compared actual figures with predicted figures, and came up with the eye-watering figure of 335,000 deaths between 2012 and 2019. Hang about, though; those figures don't include those deaths from the COVID-19 epidemic.

According to the Government's own website today (05/10/2022), there have been 177,977 "deaths within 28 days of being identified as a COVID-19 case by a positive test, reported up to Friday, 20 May 2022". Is anyone else confused by that date? 20 May 2022? Maybe I'm being paranoid; maybe it's not been updated in a while. Perhaps we don't know how many actual deaths from COVID-19 there have been since the start of the Pandemic.

There can be little doubt that the Tories cost lives during the Pandemic. A joint report by the House of Commons Science and Technology Committee and the Health and Social Care Committee condemned severe errors, including delayed lockdowns and how a test, trace and isolate system was set up. It did praise the vaccination programme, though. Funnily enough, the Government took credit for the vaccination programme while crediting the NHS with their disastrous Test and Trace program.

I'm not sure what proportion of that 177,977 needs to be added to the 335,000 death toll of the Tories; whatever figure we'll end up with, it's fair to say that the Tories are guilty of Democide.

If we look at the percentages, the Tories have done a grand job of killing half a per cent of the population; "let the bodies pile high in their thousands", indeed!

The chart at the top, it must be noted, uses a logarithmic scale. So, while the Tories aren't guilty of Mega-murder, nor Deka-mega-murder, they are guilty of Hecto-kilo-murder - and that's us they've been killing (Thank you, Wikipedia, for the pre-fixes)!

Tuesday, 28 June 2022

Cross the floor, please!

Quite frankly, we're in a pretty terrible situation in this country, and I've been trying to think of how we could get out of it. Last week I got to thinking about how to remove our MP (Lucy Frazer); I've written to her a couple of times in the past and always received a reply - she even sent a follow up when it came to Ukraine, so I sort of have semi-positive vibes about her (despite her political party). After reading up though, it seems as though there's no way I, as a constituent, can remove her, despite being the one to pay her wages; the PM can, but not I as someone who she's is supposed to represent. The salient parts of the Recall of MPs Act 2015:

...the Speaker of the House of Commons would trigger the recall process, namely:

  • A custodial prison sentence (including a suspended sentence)
    • Note that MPs imprisoned with sentences greater than one year are automatically removed due to the Representation of the People Act 1981
  • Suspension from the House of at least 10 sitting days or 14 calendar days, following a report by the Committee on Standards;
  • A conviction for providing false or misleading expenses claims.

As I noted, though, I don't hate her, I wouldn't say I like her policies and her support for our disaster of a PM, but she always struck me as being reasonable; misguided perhaps, but generally pretty decent. But things are getting serious now.

So, we can't remove her unless she seriously messes up according to the Recall of MPs Act 2015, but what else would trigger a by-election?

According to parliment:

A by-election is held when a seat becomes vacant. This can happen when an MP:

  • resigns or dies
  • is declared bankrupt
  • takes a seat in the House of Lords
  • is convicted of a serious criminal offence.

A by-election does not have to take place if an MP changes political party.

Let's take those one-by-one - I don't want her dead, and I can't see her resigning soon. I can't see her being short of a few bob. I can't see her moving into the Lords or being convicted of a serious criminal offence. It's that there last sentence, though. I'm pretty sure the Liberal Democrats would welcome her - and in light of the probably local voting pattern in any future election - it's likely to be the only way she can retain her seat in the Commons. And what's more, no by-election would be triggered either.

Now, who's shell-like should I whisper this to?

Why leave it at that - the current working majority is 75 - we'd only need a few Conservatives to either see the writing on the wall - or grow a conscience - before we could oust our PM. Christian Wakeford did it and 148 have no confidence in Johnson, if only a third of those voted with their feet and joined another party, then we might be on the start of recovering.

Might it be time for our MPs to stand up for their constituents rather than bolstering the reign of lying Johnson (I do like Rory Stewart - the Tories messed up by not electing him!)?

Friday, 25 March 2022

FizzBuzz, my arse

If you ever look me up on Goodreads, you'll find that I took an age to read The Fizz Buzz Fix: Secrets to Thinking Like an Experienced Software Developer.. I was primarily interested in reading about the FizzBuzz problem and found the rest exhausting (I wonder if they do it as an audio version?). Anyway, I found myself pondering it again recently and came across this version by Brandon Morelli:

for(let i=0;i<100;)console.log((++i%3?'':'fizz')+(i%5?'':'buzz')||i)

Aye, it's brilliant, but all approaches seem to end up using a For loop, and I got to thinking about alternatives. Thanks to messing about with other coding challenges, I clocked the way of generating an array, then thought about using that generated array with a forEach:

Array.from({length: 100}, (_, i) => i + 1).forEach(i => { 
  console.log((i%3 && i%5) ? `${i}` : `${i} ${(i%3 ? '' : 'Fizz') + (i%5 ? '' : 'Buzz')}`) 
})

This method has the benefit of not adding an extra space after the number for those numbers that aren't divisible by three or five. The forEach seems as fast for numbers up to 100 during testing and faster for numbers up to 1000.

The images of code are from https://chalk.ist, bloody brilliant aren't they?

Wednesday, 24 February 2021

Saturday, 13 February 2021

Vuetify and MDB

Both Vuetify and MDB implement the Material Design Design Language. Whereas Vuetify implements Material Design using the Vue JavaScript framework, MDB implements it atop the Bootstrap CSS framework. We're looking at a few specific terms there aren't we? We have JavaScript and CSS frameworks and Design Language - but what do we mean by those terms.

I covered the primary three JavaScript frameworks in my previous book, and there is no shortage of articles comparing and contrasting Angular, React and Vue (the three most popular JavaScript frameworks at the time of writing). A JavaScript framework provides a developer with a blueprint, and often concrete artefacts, to use when building an application. Rather than coding everything from the ground up - perhaps utilising functions from a JavaScript library - a JavaScript framework offers structure (the degree and rigidity of this structure depend upon how opinionated the framework is) that will then be decorated with the application's business logic.

JavaScript frameworks are a vast area of contention so I should note that I use VueJS the most because it's the one I use professionally. I prefer it because of familiarity and because it has the lowest adoption barrier - seemingly being the closest to VanillaJS. Again, their primary benefit is that they allow developers to hit the ground running in terms of how an application will be structured, especially those frameworks that are more opinionated. We've used the word "opinionated" a few times now - it is a term which is closely related to the friction developers find when developing applications. If the developer follows the framework designers' accepted application design, they feel less friction while developing the application. Should they attempt something outside those guidelines, and the framework be very opinionated, then they can find the going harder; they will feel more friction. The constraints of an opinionated framework can be comforting; depending upon the confidence of the developer. JavaScript frameworks do involve some measure of learning as well as obedience to the design dictates of their designers; the less opinionated the framework, the less deference is required to others' decisions, and the developer needs to have more confidence in their abilities. That is not to suggest that skilled developers don't use frameworks - just that they might have chosen their framework because it conforms with their preferred approach.

Similarly, CSS frameworks make the developer's life convenient, and they do that by removing an awful lot of the fear, uncertainty and dread at the start of a project. Initiating a project can be terrifying and might be analogous to an artist creating new work, being confronted with - and subsequently terrified of - the blank canvas. If you've been provided with a ready-made tranche of CSS, then a significant number of visual design and development decisions have already been made for you. While comforting, it is like being swaddled in a vast blanket and can be somewhat constricting. I can't be alone when I noted that many sites started to look like facsimiles of the Bootstrap site just after the framework's release.

This tendency to homogenisation is, to a degree, ameliorated by Bootstrap theming. MDB takes this theming to the next level by adding custom elements and components not ordinarily available within Bootstrap, excluding multiple external libraries.

Criticism of both CSS and JavaScript frameworks is rife and, to an extent, understandable. The constraints they provide, while offering countless boons to the developer, might explain their proliferation. Developers can be opinionated - how else might you interpret the continuing arguments over the relative benefits of Vim over Emacs. Personally, if I have to log into a Linux server, I generally use Nano, much to the disdain of friends who have spent the time to learn the minutiae of Vim or Vi. The criticism means that developers sometimes seek to break out of those confines while, in turn, constraining others within similar bonds of conformity by developing competing CSS or JavaScript frameworks. One has to appreciate such dedication. Developing a framework is a challenging and often thankless task - especially as, once birthed, they are likely to be exposed to a vast ecosystem of other frameworks, all competing for developer adoption.

Developers will be exposed to multiple CSS and JavaScript frameworks throughout their career - either by choice or imposed by their employer. As such, a new developer is left in a quandary about what to learn. The web fundamentals (HTML, CSS and JS) can sometimes be left behind in the scramble to learn the latest framework with the most-posted jobs - concentrating on learning the fundamentals provides a foundation where the developer can learn to appreciate the relative merits of a CSS or JavaScript framework. I once was asked to implement a CSS framework, which I will not name, which was very concrete and opinionated in its naming convention for the classes used. Those classes were the be-all and end-all of the framework and wholly dictated the appearance of elements in the UI. The developer could add class attributes to HTML elements to make them act in ways contrary to how they should by default, which left me feeling very uncomfortable.

That's not to say that developers should ignore innovations within the field, but discernment is required before jumping on to the latest bandwagon. Indeed, the new developer's primary focus should be the fundamentals of HTML, CSS and JS - in that order. Your first professional role, or independent study, will likely provide more than enough exposure to CSS and JavaScript frameworks.

Now that we're more aware of the frameworks lets look at Design Languages. Nate Baldwin suggests that if you have spent any time developing anything on the internet; you have either already created a Design Language of your own, or implemented someone else's. That includes creating an eBay listing or implementing a custom frame on your Facebook Profile. Baldwin's article goes into Design Languages' details. He notes that they are made up of many disparate elements in the same way that our written or spoken languages are. He also points out that, despite their ubiquity, the visual interfaces that Design Languages influence are remarkably complex mechanisms to glean and impart information. As such, we need to be conscious of their impact on our users and aware of their importance.

Being made up of many various elements, a Design Language is a tricky beast and worth studying. Even the name itself can be problematic, with designers calling them Design Languages or Design Systems - some front-end frameworks are even worthy of the name Design Language and cod;tas host a curated list of them. I've used three of the twenty-nine Design Languages listed during my career to date (at the time of writing). Still, of those three, I keep returning to Material Design - though I'm becoming more and more enamoured of IBM's Living Language. I should also note that I've also developed within the constraints of private, corporate, Design Languages for clients, some of which went so far as to have distinct and restricted corporate typefaces.

As an aside, I should also note that when I worked primarily on local government contracts, the predominant thematic colour was purple. After all, Purple was historically restricted to royalty and the elite due to the dye's original exorbitant costs. Thankfully I was no longer working in such a milieu when the UK Independence Party co-opted that particular colour. One can only imagine that the only solution to theming such sites today is to use the whole rainbow of colours represented on Wikipedia's list of United Kingdom political party meta attributes.

As a further aside, I spent some time thinking about political parties' colours in the UK. I wrote a blog post with, not my thoughts per se, but my findings, from the Wikipedia article linked above and display the political parties, sorted using their hue, saturation and lightness (HSL) values.

Working within a Design Language's strictures is similar, but not the same as working within a CSS or JavaScript framework's bindings. It is related in that working with a Design Language means that the overarching application has a consistent look (in the same way as working with a CSS framework) and feel (in the same way as working within a JavaScript framework). Further, introducing elements from other Design Languages is likely to present your users' to some discordance and lead to confusion in the same way as CSS and JavaScript frameworks don't often play well together.

That is not to say we should not seek to challenge our users by introducing innovation. But those challenges should be sprinkled sparingly through the application, rather than at every turn of the user. Using a Design Language, we help our users feel confident that their interaction will lead to expected results by allowing them to feel confident in the application's consistency. Further, a documented Design Language will allow other team members - should you enjoy work with others - feel as though they know how to progress with preliminary development before the input of a dedicated front-end developer. John Rhea discusses the introduction of dissonance to an application in his book Beginner Usability: A Novice's Guide to Zombie Proofing Your Website. He notes that users will be familiar with interacting with websites in specific ways, though interacting with previous websites. He also notes that to introduce dissonance, one must already be conscious of the rules implied by a Design Language.

But what is a Design Language? In the case of Material Design, Material is the metaphor which inspires the Design Language. Real-world objects act as the inspiration for user interface elements. Content is organised using cards, lists and sheets, navigations occurs when users interact with navigation draws and tabs; actions are initiated using buttons. Nearly all elements have a subtle rounding because, in nature, right angles are rare. I'm conscious that answering what a Design Language is is difficult to define; it is an aesthetic feeling towards an application and is made up of visual and conceptual standards. UXPin, the above quote's originator, says that a Design Language collects and standardises user interface components and patterns, a style guide, and some semantics documentation. Both UXPin and Gleb Kuznetsov note that a Design Language must relate to the brand's corporate identity. Should you be tasked with developing an application for a brand, you must examine their other assets - physical or internet-based. This examination will furnish you with a feeling about how your application should look, even if it's only related to any logos to be used or colour-schemes to implement.

We started this by examining what we mean by CSS frameworks, JavaScript frameworks and Design Languages; we'll now look at the relationship between MDB and Vuetify and Material Design. Both MDB and Vuetify implement the Material Design Language, using significantly different techniques. Up until the release of version 5, MDB also required the inclusion of the jQuery JavaScript library. The Bootstrap CSS framework itself required jQuery before version 5; now it only needs the Popper JavaScript library to enable proper positioning of tooltips and popover elements. MDB now has its own, dedicated, JavaScript library and no longer requires jQuery.

MDB adds Material Design concepts to the Bootstrap CSS framework along with a significant number of discrete, JavaScript-powered, user interface elements. Vuetify does pretty much the same but adds Material Design principles to the Vue JavaScript framework. Bootstrap and MDB's reliance on JavaScript means that both approaches aren't all that different, especially when considering the initial reliance Bootstrap had on jQuery. The primary differences are how Vuetify forces the developer to write the application. MDB decorates the HTML, whereas Vuetify replaces common HTML elements with its components. If building with HTML is analogous to building with Lego - which it can sometimes seem to be - then creating your first application with Vuetify is similar to building with a completely different construction toy such as Meccano.

Perhaps I might be accused of being an HTML purist, but using a v-container element is little different to using a div and adding a class of container - but it does seem to be a case of replacing HTML elements for the sake of it. Vuetify does have a sensibly naming convention so that you can mostly guess what is required next while building your application. A v-container likely needs to have a v-row within it, and that v-row is crying out for at least one v-col. You know, seeing as both Bootstrap and Vuetify both share a twelve-point grid system, that the v-col will have a cols attribute with a number between 1 and 12 as its value. But why bother with separate elements when adding a hierarchy of div elements with the classes of container, row and col-* will work just as well? It all smacks of overkill and using custom elements for the sake of it.

I guess that there's not a great deal to differentiate the two approaches to implementing Material Design. Vuetify has the closest affinity to those developers already used to working with Vue. In contrast, MDB is likely to feel the most natural for those developers used to traditional application development using so-called monolithic application structures, which don't take advantage of Single Page Application (SPA) architecture. I am conscious that I don't use SPAs professionally. To a greater or lesser degree, I'm not upset about not working with a SPA though, and I feel an affinity with Chris Ferdinandi when he notes that:

"Browsers are an amazing piece of technology. They give you so much for free, just baked right in."

"Single page apps break all that, and force you to recreate it with JavaScript."

Along with Vuetify, another prominent Vue Component Library implements the Material Design Language, Vue Material. Vue Material is closely related to, and partners with Creative Tim who develop Vue Material Kit. This situation mirrors MDB, as they also offer a paid-for product with more components.

Whatever your chosen CSS or JS framework or Design Language, the important thing is not to confuse your users too much while keeping your clients happy.

Saturday, 6 February 2021

Political Spectrum

I'm doing some writing on design languages at the minute and, in an aside, I talked about working for UK local governments in a previous job. At that time, we used to be tasked with making the primary thematic colour purple because no political party used it. This usage was in the days before the rise of UKIP you see. Anyway, a purple cloth used to be reserved solely for royalty because of the die's exorbitant cost. Finding a list of the colours of all UK political parties on Wikipedia got me thinking about the amount of scripting I'm doing in the context of the 1000 miles in 2021 challenge. So I copied the table from Wikipedia and threw it into a Google Sheet. I converted the colour names into their HEX values then converted them to HSL so that I could sort them properly. Sorted first by Lightness, then Saturation and finally by Hue - I finally output an HTML link which I could display. The following is the sheet script I used to generate the links, and the result is above.

const RGBToHSL = (r,g,b) => {
  // Make r, g, and b fractions of 1
  r /= 255;
  g /= 255;
  b /= 255;
  // Find greatest and smallest channel values
  let cmin = Math.min(r,g,b),
      cmax = Math.max(r,g,b),
      delta = cmax - cmin,
      h = 0,
      s = 0,
      l = 0;
  // Calculate hue
  // No difference
  if (delta == 0){
    h = 0;
  }
  // Red is max
  else if (cmax == r) {
    h = ((g - b) / delta) % 6;
  }
  // Green is max
  else if (cmax == g) {
    h = (b - r) / delta + 2;
  }
  // Blue is max
  else {
    h = (r - g) / delta + 4;
  }
  h = Math.round(h * 60);

  // Make negative hues positive behind 360°
  if (h < 0){
    h += 360;
  }
  // Calculate lightness
  l = (cmax + cmin) / 2;
  // Calculate saturation
  s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
  // Multiply l and s by 100
  s = +(s * 100).toFixed(1);
  l = +(l * 100).toFixed(1);
  return ["hsl(" + h + "," + s + "%," + l + "%)", h, s, l];
}

const hexToRGB = hex => hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => '#' + r + r + g + g + b + b).substring(1).match(/.{2}/g).map(x => parseInt(x, 16))

const myFunction = () => {
  const returnSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1')
  const rowCount = returnSheet.getLastRow()
  for (let i = rowCount; i > 1; i--) {
    const rawValue = returnSheet.getRange('C' + i).getValue()
    const c = hexToRGB(rawValue)
    const hsl = RGBToHSL(...c)
    const url = returnSheet.getRange('B' + i).getRichTextValue().getLinkUrl()
    returnSheet.getRange('D' + i).setValue(rawValue.toUpperCase())
    returnSheet.getRange('E' + i).setValue(c[0])
    returnSheet.getRange('F' + i).setValue(c[1])
    returnSheet.getRange('G' + i).setValue(c[2])
    returnSheet.getRange('H' + i).setValue(hsl[0])
    returnSheet.getRange('I' + i).setValue(hsl[1])
    returnSheet.getRange('J' + i).setValue(hsl[2])
    returnSheet.getRange('K' + i).setValue(hsl[3])
    if(url){
      returnSheet.getRange('L' + i).setValue(`<a href="${url.replace('/meta/shortname', '').replace('Template:', '')}" target="_blank" style="background-color:${rawValue.toUpperCase().trim()};width:1px;" title="${returnSheet.getRange('B' + i).getValue()}"></a>`)
    }
  }
}