Christian Heilmann

You are currently browsing the archives for the General category.

Archive for the ‘General’ Category

Writing a dog picture browser in ~200 lines of code

Thursday, October 29th, 2020

Dogs are excellent, and great people. When I came across the Dog.ceo API of free dog images sorted by breed the other day, I had to do something with it. So I did. I give you the dog browser:


Dog browser Desktop version
Dog Browser Mobile version

You use it by clicking the image to get a random dog picture or typing into the box to select the breed and filter the results.

You can check the source on GitHub and play with it yourself.

Here’s how I went on about it…

Getting the data

Looking through the API documentation I found three endpoints I needed to play with:

The data is in JSON and pretty straight forward, it is an object with a status and a message property. For image lookups the message is the URL to the image. For the “all breeds” lookup an object with all the names as keys and an array of possible sub-breeds. One annoyance is that the breeds are in lowercase.

Planning the HTML

I wanted a way to display the image of the dog, its breed and allow the user to filter by breed.

Buttons are great, because they are keyboard and mouse accessible. They also can contain any other HTML element. For the display of the image I chose the following:

<button class="loading">
  <h2></h2>
  <img src="" alt="Good boy/girl">
  <p>Click for more dogs!</p>
</button>

This makes the hit area for people to choose the next image as big as I want it to be.

For choosing the breed, I had the problem that the list of breeds is huge and there may be sub-breeds. At first, I thought of a select box that shows a second one when there is a sub-breed available. That is both cumbersome to use and annoying to develop. So I chose an input element connected to a datalist. This is HTML5’s autocomplete.

<form>
  <label for="breed">Dog breed:</label>
  <input list="allbreeds" id="breed"></input>
  <datalist id="allbreeds"></datalist> 
  </form>

Styling the interface (treats.css)

You can take a look at the source of the CSS for all its glory (or lack thereof), but I used a few tricks that may be of interest.

button {
  max-width: 90vw;
  min-height: 50vh;
  width: 100%;
  cursor: pointer;
  position: relative;
  /* … More … */
}

I give the button a minimal height of half the screen and limit it to 90 % of the window. I give it a cursor of pointer to tell people to click. And I position it relative to allow for some clever loading message styling later on.

button img {
  border-radius: 10px;
  margin: 0 auto;
  object-fit: contain;
  max-height: 60vh;
  min-height: 200px;
  max-width: 90%;
}

I give the img a max and min height and a max width that gives it some breathing space. The object-fit: contain ensures that the image doesn’t get stretched.

button.loading img {
  opacity: 0;
}

During loading I hide the image just as a nice to have. Talking about loading, here’s a neat little way to create a “loading” overlay.

button.loading:after {
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  content: 'Loading...';
  background: rgba(0,0,0,.8);
  color: white;
  position: absolute;
  top: 0; left: 0; 
  right: 0; bottom: 0;
}

As the button has a position of relative, we can create the overlay using CSS generated content. We position it absolutely and set top, left, right and bottom to 0. This covers the whole button and makes sure users can’t click it again while the image loads. The flex settings ensure that the “Loading…” message is bang in the centre.

button.error:before {
  content: '&#x26a0;&#xfe0f; Oh no! No dogs found, try another breed!';
  color: firebrick;
  display: block;
  margin: 5px;
  border: 2px solid darkred;
}

I also use CSS generated content for an error state.

One last thing is a media query to display the form next to the button when there is enough space or above on smaller devices:

@media (min-width:600px) {
  section {display: flex;}
}

Making it work with JavaScript (walkies.js)

I may change this code in the future, so make sure to check the source on GitHub from time to time, but here we go.

const breed = document.querySelector('#breed');
const imagecontainer = document.querySelector('button img');
const breedinfo = document.querySelector('h2');
const button = document.querySelector('button');
const datalist = document.querySelector('#allbreeds');
 
let url = 'https://dog.ceo/api/breeds/image/random';

I store references to all the HTML elements the script will touch. I like doing that as it means I can change the HTML later on.

I define the URL to get images from as the one giving you a random dog picture.

const getbreeds = breeds => {
 fetch('https://dog.ceo/api/breeds/list/all')
  .then(response => response.json())
  .then(data => {
     seedbreedsform(data.message);
   })
};

The getbreeds function uses the API to get all the names of available dog breeds. I use fetch() (naturally) to load the JSON, parse it and send the result to the seedbreedsform() function to seed the form with this data.

const ucfirst = str => {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

One annoyance of datalist is that it can’t get styled. As the dog breeds are all lowercase in the API, I’m using a small function to capitalise the breed and sub breed. With dropdowns this could be done in CSS (its natural place) and hopefully we will get there one day.

const seedbreedsform = breeds => {
  let out = '';
  Object.keys(breeds).forEach(b => {
    out += `<option value="${ucfirst(b)}"/>`;
    breeds[b].forEach(s => {
      out += `<option value="${ucfirst(b)} - ${ucfirst(s)}"/>`;
    });
  });
  datalist.innerHTML = out;
  breed.addEventListener('change', findbreed);
};

I loop over all the keys of the breeds object the API returned and create an option in the datalist for each. The keys are the names of the breeds. If there are sub-breeds their value is an array of more breeds. I loop over these arrays and create an option with the value of “Breed – sub-breed”. Once all the options are there, I set the innerHTML of the datalist to the resulting string. This, effectively, gives the user an autocomplete of all the available breeds.

I add an event listener calling findbreed when the user selects a breed from the autocomplete.

const findbreed = _ => {
  let name = breed.value;
  name = name.replace(' - ', '/').toLowerCase();
  url = `https://dog.ceo/api/breed/${name}/images/random`
  getdog(); 
};

As the value of the input element is the readable version of the data, I have to undo this. I replace the ” – ” with a slash, lowercase the whole string and assemble the URL to get the image from the API. I change the URL to this more specific one and call the getdog function.

button.addEventListener('click', getdog);  
imagecontainer.addEventListener('load', e => {
  button.classList.remove('loading');
});

The getdog function is the main function to load an image and add it to the button element. When I click the button again, I’d want another image. Thus, I need to add an event handler to the button to call it. The getdog function changes the src attribute of the image container to load an image. That’s why I need a load event handler on the image to change the state from loading to finished.

const getdog = _ => {
  button.classList.remove('error');
  button.classList.add('loading');
  fetch(url)
  .then(response => {
    if (response.ok) {
      return response.json();
    } else {
      button.classList.remove('loading');
      button.classList.add('error');
    }
  })
  .then((data) => {
    imagecontainer.src = `${data.message}`;
    let bits = data.message.split('/');
    bits = bits[bits.length-2]
           .split('-')
           .map(b => ucfirst(b))
           .join(' - ');
    breedinfo.innerText = bits;
  })
};

I remove any error CSS classes that may be applied to the button and add a ‘loading’ one. I then call the API using fetch.

If the response is not good I remove the ‘loading’ class and add an ‘error’ one.

If the response is ‘ok’, I set the src of the image in the button to the message that came back from the API (the URL of the image). I then need to do some conversion to display the breed of the current, random dog image.

Here’s how that works:

URLs could be a mix of one breed dogs or sub breed dogs. Those with a sub breed have a hyphen in them. For example:

https://images.dog.ceo/breeds/cockapoo/Scout.jpg
https://images.dog.ceo/breeds/spaniel-cocker/n02102318_5690.jpg

I split the url at the slashes, and get the one before the last one, in this case “cockapoo” or “spaniel-cocker”. I split this one at the hyphen and send each to ucfirst to capitalise them. I then join them again with a ” – ” and display the resulting string.

The last thing to do is to make sure that any error in the form submission doesn’t reload the page.

document.querySelector('form').addEventListener('submit', e => {
  e.preventDefault();
});

And to load the first dog image and get the list of breeds.

getdog();
getbreeds();

Fun with web standards

There you go. A dog browser in a few lines of CSS and JavaScript and with zero dependencies (except for the dog API, of course). I am pretty sure this can be improved a lot, but I had fun doing it and it is wonderful to see what the web comes with out-of-the box.

Getting dicey – how I failed to write a perfect dice throw simulator and how that is totally OK

Monday, October 19th, 2020

Yesterday we wanted to play some dice games and I realised I had none in my house. So I spent a bit of time on developing a Dice Throw Simulator

A dice throw simulator in the browser.

The code is also available on GitHub and probably nothing to win a job interview with, but it was fun to do, and hopefully you’ll also find it at least interesting.

The fascinating bit to me about doing something like this is the predictability of responses. The biggest thing developers love to get riled up about is that Math.random() doesn’t really give you any proper random results. The next bit that always comes is why I didn’t use a 3D library to make some really cool 3D rolling dice. All of this has been discussed to death quite some time ago, so here is why I developed yet another dice simulation that is horribly flawed.

  • It was fun!
  • I got myself more familiar with vw sizing and flexbox doing it
  • I found out the Firefox is OK with translate values not having a comma in between them whereas Chromium complains
  • I once again fell in love with focus-within and labels automatically connecting huge screen estate with a tiny checkbox
  • I really like how powerful JavaScript is these days. Gone is the need to test if something is supported and how. I find myself using a lot less if statements than ever
  • Using GitHub and GitHub Pages means I don’t need to spend any money on hosting
  • I wanted a way to lock some dice in and re-throw, much like you would when you leave them out of the cup to get to a certain goal. Both Google’s excellent solution and Random.org’s much more random solution don’t have that feature.
  • None of these can ever replace the fun that is throwing some physical dice around a cup and tilting it over. Or learning about new parts of your flat when once again one of them rolled off the table.

Do you also want to code something just for fun? Do it! Don’t get discouraged by people who always know a way to make it better – that’s the easy part.

“How normal am I” is an excellent security and privacy resource about Artificial Intelligence on the web

Thursday, October 8th, 2020

https://www.hownormalami.eu is amazing.

Funded by the EU, it asks you to share your camera and then continues to tell you what different “AI” algorithms think of you – age, gender, BMI and so forth. It shows a video at the same time explaining what is being analysed and how these algorithms fail. It does some more clever things in terms of privacy but I don’t want to spoil it. If you want to get an insight into what is being recorded of you and how skewed the information is, this is a great resource. Thanks to Emily Holweck for sending it to me.

How to be a more effective developer week (tools and tricks)

Friday, October 2nd, 2020

Larry (RIP) my dog sleeping

I’ve had a busy week.

Sharing developer tips at Microsoft Create: Serverless

Wednesday I hosted an open Mic session at the Microsoft Create: Serveless event together with Marie Hoeger. The topic was “Developer Hacks and Tools we wished we knew sooner” and we had about 30 attendees sharing their best tricks.

I put up a collection of what was discussed at dev.to.

Virtual talk in Scotland

Yesterday started with me giving a talk at ScotSoft’s CmdR conference. In it I talked about how we can improve ourselves as developers by embracing better tooling. Tools that prevents us from making mistakes as we make them.

I compared it to all the things cars do for us these days. Last weekend I had a 500km road trip in a brand new Audi and I was amazed and overwhelmed with all the options. But I soon started to enjoy the convenience of cruise control, lane assist and distance measuring.

The slides are available on Noti.st :

View Sharpening the saw – how tooling can make you a better developer on Notist.

Here are the resources I talked about with some explanations why they are relevant:

  • CGP Grey: the Simple Solution to Traffic – a great explanation how traffic jams happen due to human error
  • Eight Cognitive Biases in Software Development – interesting read to avoid some pitfalls and information why we are so bad at estimating
  • Visual Studio Code – well, it is the bomb.
  • Webhint online scanner – an online scanner to test your web products for all kind of issues about accessibility, performance, security and compatibility
  • Webhint for Visual Studio Code Extension – the same tool but running inside VS Code flagging up that you are making mistakes whilst you make them. The great thing is that it not only tells you that something is wrong, but it also explains the why and gives you links to more in-depth information
  • Webhint in Edge Developer Tools – integration of Webhint into the Chromium developer tools showing site problems as issues.
  • Editor in Developer tools – did you know that the Chromium developer tools have a full editor in them where you can write code, inspect code from the web and even override external files with local ones to try out fixes without having server access?
  • Breakpoint Debugging in Developer Tools – breakpoints are so much better than using console log. They give you the whole picture and stop script execution until you learned what you wanted to know.
  • DOM Breakpoints Debugging – you can set breakpoints coming from the HTML generated interface thus debugging your app as you interact with it
  • Command Menu in Devtools – developer tools in browsers have become complex, you can use keyboard shortcuts to access all its features without needing to know which pane or menu the functionality is in.
  • Keyboard Shortcuts in Developer Tools – the tools inside developer tools all have keyboard shortcuts
  • Snippets in Developer Tools – you can set up a collection of snippets for your project inside developer tools
  • Mobile Device Emulation – you can simulate different devices, connection speeeds and interaction models inside developer tools. This also comes with a screen ruler, media query inspector and option to create screenshots.
  • You can simulate changes to the operating system settings in developer tools withouy having to change your OS: Dark/Light Mode, Reduced Motion and Visual Deficiencies. This allows you to quickly get insight if your product is hard to use by someone who needs to customise their computer to their needs
  • Git Version Control in VS Code – having a visual interface to Git allows you to concentrate on good commit messages rather than worrying about command typos
  • Integrated Terminal in VS code – you don’t need to jump between editor and terminal, pressing cmd|ctrl and backtick opens a terminal right in the folder where your files are
  • Network Console in Browser tools – an experiment in Developer Tools allowing you to intercept, edit and replay any network request
  • Microsoft Edge Developer Tools Explainers – innovation in the open, here you can see what the team is working on and what comes soon
  • Edge Developer Tools for VS Code – embedding the developer tools of the browser into the editor, so you don’t need to switch

I will talk much more about the last bit in my next blog post as I am chuffed that we managed to get this from a preview and wild idea to production within a year.

Checkboxes make excellent buttons

Thursday, September 24th, 2020

checking off a list with a marker

Photo by TeroVesalainen

I like checkboxes – they give me a simple way in a tool to turn modes on and off without using much space. I especially like it that you can style them with CSS without jumping through hoops like you need to with buttons.

I like to use checkboxes as buttons. And here’s how.

A checkbox is a binary state. It is checked or not. So instead of reading out the state in an event handler, I tend to read the checked property.

What does this mean?

Take a look at this codepen :


See the Pen
Checkboxes as easy to style buttons
by Christian Heilmann (@codepo8)
on CodePen.


Looks neat and works. Let’s take a look at the how.

The first thing I do is to ensure that my code is accessible. That’s why every checkbox needs a label to explain to assistive technology like screen readers what it is.

<input type="checkbox" id="doublewide">
<label for="doublewide">Double width</label>
<input type="checkbox" id="doublehigh"> 
<label for="doublehigh">Double height</label>
<div id="box"></div>

This also has the real practical upshot that when I click on the label text (which is normally much bigger than the checkbox) I change the state of the checkbox. This also helps a lot on mobile devices. From a look and feel point of view, it means I don’t need the checkboxes any longer, so let’s hide them off screen:

/* Hide checkboxes */
[type=checkbox] {
  position: absolute;
  left: -50vw;
}

We style the labels to look “not selected” or greyed out:

label {
  background: #ccc;
  padding: 5px 10px;
  color: #333;
}

And we colour them when the checkbox is checked (let’s also add a transition to make it look smoother):

/* Selected */
[type=checkbox]:checked + label {
  background: #369;
  color: #fff;
  transition: 400ms;
}

Quick aside: you can also style them to interact to keyboard users focusing on the checkboxes and to mouse interaction on the label itself:

[type=checkbox]:focus + label {
  background: #9cf;
  color: #000;
}
[type=checkbox] + label:hover {
  background: #9cf;
  color: #000;
}

Now on to the JavaScript interaction. First, we need some references to the checkbox DOM elements (it makes sense to cache that as reading the DOM is expensive).

const dw = document.querySelector('#doublewide');
const dh = document.querySelector('#doublehigh');

We’ll define a change function to be called every time one of the checkboxes is clicked. In this one we read out the checked state of the checkboxes and react accordingly. In this case, changing the width of the box.

const change = _ => {
    box.style.width = dw.checked ? '100px' : '50px';
    box.style.height = dh.checked ? '100px' : '50px';
 };

The last thing to do is to add Event Listeners to the checkboxes to call the change function:

dw.addEventListener('click', change);
dh.addEventListener('click', change);

We could even use Event Delegation instead and apply this to all checkboxes. That way we don’t need to add a lot of listeners and we can dynamically add and remove checkboxes without having to re-iterate over them.

document.body.addEventListener('click', e => {
  if (e.target.type === 'checkbox') {
    change();
  }
})

Nothing magical here, but I really like the fact that instead of having to store the state myself, all I need to do is read the checked state and leave the interaction to the browser.