Code Monkey home page Code Monkey logo

blog's Introduction

Blog

Based on Remix, getting data from GitHub, deploying to Netlify.

blog's People

Contributors

neokoenig avatar perdjurner avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

neokoenig

blog's Issues

New blog-like thing

Hello world!

I will be using this little space I've created here mainly as a way to write down notes on web development to future me. Rather than searching through old projects for code, I'll start posting here so I always have it handy.

The site itself is very much a work in progress. At the moment I'm using the GitHub issues API as the backend for the blog. In other words, I post an issue there, it ends up here. The frontend is plain HTML, CSS and JavaScript. No frameworks, no bundling. Nice and simple.

If you're interested in doing something similar, you can have a look at the repository, and if you have any thoughts, get in touch with me through Twitter.

Thanks for reading.

Ignoring TypeScript files when deploying to Azure

Writing this as a note to myself mostly (as most things on this blog).

I ran into an issue where my .NET Core app could no longer be deployed to Azure. Turns out I had recently introduced some TypeScript files that were now failing in the Kudu / MSBuild step (due to differences in the version of TypeScript used on my machine versus the one used on Azure I believe). Since those TypeScript files are related to Vue and not the .NET Core app, I simply wanted the build step to ignore these files.

To do that, I added the following to my .csproj file.

<PropertyGroup>
  <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup>

Emojis in .NET Core / MySQL

To support emojis when using MySQL in .NET Core (version 2.2 in my case) you need to do a few things.

First of all, add CharSet=utf8mb4; to your database connection string. In other words, your code in Startup.cs should look something like this.

string db = @"Server=domain.com;Database=db;Uid=user;Pwd=pass;CharSet=utf8mb4;";
services.AddDbContext<AppDbContext>(options => options.UseMySQL(db));

You also need to make a couple of database changes. The charset needs to be set to utf8mb4 both for the column you are storing the emojis in and as the default for the entire database.

Ideal number of characters per line

To make your website text as readable as possible it's important to use the right number of characters to display on each line.

If your lines are really long, the reader will have a hard time finding the next line to read (because it's too far away from the beginning). If they're too short however, the reader will lose their rhythm because their eyes will have to track back too often.

But is there a magic number to aim for?

Most experts seem to indicate that having anywhere from 45 to 120 characters per line is fine. In other words, it won't cause the reader any noticeable problems if you keep it within that range. 55 to 65 characters is the most optimal for print, but there are studies that seem to suggest you can get away with a slightly higher number of characters on the web. The official W3C recommendation is to keep line length under 80 characters.

Given all that, I tend to aim for around 70 characters per line.

I do make an exception when targeting phones though. Rather than making the font really small to try and accommodate the 70 characters per line rule, I'm ok with ending up at around 50 characters per line instead.

Sorting an array of objects

Also include how to sort by multiple properties (like a boolean first and then name within that for example).

Highlight table row when hovering over specific table cell

Highlighting a table row when hovering over it is easy, you just use the :hover CSS pseudo-class. But what if you only want to highlight the table row when you hover over a specific table cell? It's a little more difficult because you can't achieve it using just CSS, you have to throw in some JavaScript as well.

<style>
  .highlight {
    background: #e2e8f0;
  }
</style>
<table>
  <thead>
    <tr>
      <th>Column 1</th>
      <th>Column 2</th>
      <th>Column 3</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Data 1</td>
      <td class="hoverable">Data 2</td>
      <td>Data 3</td>
    </tr>
  </tbody>
</table>
<script>
  function highlightRow(event, fn) {
    event.target.closest("tr").classList[fn]("highlight");
  }
  document.querySelectorAll(".hoverable").forEach((elm) => {
    elm.addEventListener("mouseenter", (e) => highlightRow(e, "add"));
    elm.addEventListener("mouseleave", (e) => highlightRow(e, "remove"));
  });
</script>

Open example page.

Comment notes

Always above code, explain what block of code does
Blank line after block of code
On the right only if it fits and is a list of things
Use pseudo-comments, leave the relevant ones in place after coding
Explain in variable / function naming when possible / pragmatic, comment when not

Dump object

ObjectDumper.net
console verbosity
iOutputHelper

Nullish coalescing operator in Vue templates

The nullish coalescing operator can be really useful when you want to display, for example, "N/A" when a value is null or undefined, but not when it's 0.

<template>
  <p>{{ val ?? 'N/A' }}</p>
</template>

<script>
export default {
  data() {
    return {
      val: 0
    }
  }
}
</script>

Note that you need Vue 3 for this to work in templates.

Get the domain from a URL

Use the trick to create an a element and get hostname, then regex to get the last two parts of the domain name.

Git ignore a folder with exceptions

Usually when working with .gitignore files it's pretty straight forward, you ignore some folders and / or files and you're done. But what if you want to ignore everything inside a folder except for specific folders and files inside it?

Here's how you can do that.

folder/*
!folder/tracked-folder
!folder/tracked-file.js

As you can see the ! prefix is used here. It negates the pattern so that any matching file excluded by a previous pattern will become included again.

How to reset Entity Framework Core migrations

Delete all records in the __EFMigrationsHistory table.
Delete all files in the Migrations folder.
Run dotnet ef migrations add Initial.

So far we haven't made any database changes at all, just created the migration file itself. The problem now is that you can't run that migration because the tables already exist in the database.

Comment out everything in the Up method.
Run dotnet ef database update.

Now you have applied the migration but nothing in the database changed since the code was commented out.

TypeScript and functional React components

Here's an example of how I typically add TypeScript types to a functional React component.

import { ReactNode } from "react";

type CmpProps = {
  children: ReactNode;
  classes: string;
};

export function Cmp({ children, classes }: CmpProps): JSX.Element {
  return <div className={classes}>{children}</div>;
}

I prefer this method over using the built-in React.FC type because now I can specify that the children property is required, rather than having it optional as it is in React.FC. This way, if you forget to include the children for the component, TypeScript will scream at you (or if you include children when it should not be included).

Also, note the JSX.Element return type for the function. That can be useful to have in there to catch some silly mistakes made with formatting for example. If you didn't have it specified you would still likely get a TypeScript error somewhere but it would be from where you consume the component instead, which is a little less obvious.

Checking for nulls in C#

Checking for nulls is one of those things that leads to a lot of boilerplate code in C#. As such, I want it to take up as little space as possible so it doesn't distract me from the "real" code.

Here's my current favorite one-liner way of doing it.

_ = myArg ?? throw new ArgumentNullException(nameof(myArg));

This makes use of discards (_) and the null-coalescing operator (??).

Discards are placeholder variables that are intentionally left unused.

The null-coalescing operator returns the value on the left side if it isn't null, otherwise it returns the result of the operand on the right side.

In other words, all we're doing here is placing the value of myArg in an unused variable if it's not null, and when it is null, we throw an error instead.

You could argue that this code is a little weird and not quite as readable as using a full if statement instead, but I think that's a trade-off worth doing in this case.

By the way, there's an even better way of checking for nulls on the horizon. A feature called "Simplified Null Argument Checking" may be coming in a future release of C# (it was removed from C# 9 at a late stage). If added, all you have to do is put ! at the end of a parameter name and it will run the same code as above under the hood.

Free up space on Azure

After an automatic deployment from GitHub, the NuGet packages remain in the .nuget folder even though they're not needed anymore. This can become a problem if you have several apps running, each having their own .nuget folder. You'll pretty quickly exceed the 1GB available space (shared D1 plan). To remove them, click on "Advanced Tools" in the menu on the left for your Azure app service. Then go to Debug Console, CMD and you should see a file explorer where you can delete the .nuget folder.

Better way is to build with a no cache setting?

Sticky footers using Tailwind

Creating a footer that sticks to the bottom of the page is fairly easy when using Tailwind (and to be fair, it's not too hard with plain old CSS either).

You can use something like the code outlined below.

<body class="flex flex-col min-h-screen">
  <header>
    Header content...
  </header>
  <main class="flex-grow pb-12">
    Main content...
  </main>
  <footer>
    Footer content...
  </footer>
</body>

That will set the body to always be at least the height of the viewport and the main element to fill the available space height wise (using Flexbox).

The bottom padding on the main element is optional, but it's probably something you want to have in order to create some space in the layout before the footer comes.

If you want the footer to actually stick to the bottom of the screen as well (i.e. always be visible regardless of how tall the main content area is), then add sticky and bottom-0 to the footer element.

Sanitize HTML using JavaScript

If you're writing JavaScript and need to sanitize a string, here's an easy way to do it.

const html = "<p>Test</p>";
const elm = document.createElement("p");
elm.textContent = html;
const sanitized = elm.innerHTML;

The reason this works is that when you set the potentially dangerous string to the element's textContent property, it will be escaped. Then you just read it back using innerHTML and you have a safe string that you can use instead.

Better focus outline on rounded buttons

Here's a mistake I remember having made in the past. I would add some rounded corners on a button and then discover that the focus outline looked a little ugly (it would be rectangular and not follow the border radius). Then I would add outline: none to remove it, thinking I was done. Wrong!

I've now created a problem for keyboard users. If they tab to a button, they can't see that it's in focus because there are no styles to indicate that. In other words, the user interface is not as accessible as it could be.

Here's a better solution (open example page).

button {
  border: solid #d3d3d3 1px;
  border-radius: 10px;
  padding: 10px 20px;
}
button:focus {
  box-shadow: 0 0 0 1px #000;
  outline: transparent solid 1px;
}

This effectively removes the outline by making it transparent and adds a box shadow to act as the outline instead. The reason for using box-shadow is that it follows the border radius so it's no longer rectangular.

Note that we make the outline transparent instead of just removing it. This is because this way it still works in high contrast mode in Windows.

Also, if you don't like the boring 1 pixel black outline you can go for something like this instead (open example page).

button:focus {
  box-shadow: 0 0 0 3px rgba(164, 202, 254, 0.45);
}

Using a custom arrow in a select drop-down

Let me start by saying that you probably shouldn't do this. Stick to letting the browser render its default look for form elements as much as possible. They will be more familiar to your users that way. That said, let's say you've been told to customize these arrows a little bit, here's how you can do it.

select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  padding-right: 24px;
  background-image: url("data:image/svg+xml;utf8,<svg fill='%23808285' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>");
  background-repeat: no-repeat;
  background-position-x: right;
  background-position-y: 50%;
}

A few things to note...

The appearance rules will remove all browser specific styling for the select element, not just on the arrow itself. In other words, you might need to add some more CSS to get it back to looking as you want it to.

Do not use this method if you need to support Internet Explorer 11, it won't work.

You need to encode the # character that's used when setting the color using the fill attribute on the SVG element. For example, something like <svg fill="#ffffff"></svg> would become <svg fill="%23ffffff"></svg> instead.

Clickable table rows

Turns out that making a table row clickable while keeping it accessible, and allowing there to be clickable elements inside it, is a little tricky.

Here's the code I ended up with.

<style>
  tbody tr {
    cursor: pointer;
  }
  tbody tr:hover {
    background: lightgray;
  }
</style>
<table>
  <thead>
    <tr>
      <th>Column 1</th>
      <th>Column 2</th>
      <th>Column 3</th>
    </tr>
  </thead>
  <tbody>
    <tr role="button" tabindex="0" aria-label="Show row details">
      <td>Data 1</td>
      <td><button class="skip">Button</button></td>
      <td><a href="#" class="skip"><strong>Link</strong></a></td>
    </tr>
  </tbody>
</table>
<script>
  function handleEvent(e) {
    const elements = e.composedPath();
    for (const elm of elements) {
      if (elm.tagName === "TR") {
        break;
      } else if (elm.classList.contains("skip")) {
        return;
      }
    }
    const space = 32;
    const enter = 13;
    if (e.type === "click" || e.keyCode === space || e.keyCode === enter) {
      e.preventDefault();
      console.log("Do something for table row event.");
    }
  }
  document.querySelectorAll("tbody tr").forEach((elm) => {
    elm.addEventListener("click", handleEvent);
    elm.addEventListener("keydown", handleEvent);
  });
</script>

The code is accessible because you can get to the table rows using your keyboard and select them by pressing Space or Enter.

It's possible to add links, buttons etc and have their events handled normally by adding a "skip" class to them (or on a parent element).

Common Git commands

Here's a collection of some of the Git commands and workflows I use on a regular basis.

Configure Git with your credentials. This is set globally so you only have to do this once. Remove the --global flag if you need to override this information for a specific repository.

git config --global user.name "Your Name"
git config --global user.email [email protected]

Initialize a repository (in the folder you're currently in).

git init

Show current status (changed / staged files).

git status

Stage a specific file

git add file.js

Unstage a specific file

git rm --cached file.js

Stage all files

git add .

Commiting

git commit -m "Fix bug"
git log
git log --oneline

Reverting changes

git checkout abc123a (read only view of a specific commit)
git revert abc123a (add new commit to revert a specific commit, not great if another commit relies on the reverted code)
git reset abc123a (remove all commits after that commit but leave files unstaged)
git reset abc123a --hard (remove all commits after that commit, danger since code is now gone)

shift, colon and wq

Branches

git branch -a (show all branches)
git branch feature-x (create new branch)
git checkout feature-x (switch to branch)
git branch -d feature-x (delete branch if it's been merged)
git branch -D feature-x (delete branch regardless of merge status, dangerous since code can be lost)

Merging (be on the branch you want to merge into)

git merge feature-x

If conflict, edit files manually and stage file, make a commit.

get from github / gitlab etc (one folder up)
git clone https://...
git push origin master

existing local git repo:
git remote add origin https://...
git push origin master

git pull origin master
= fetch code so local remote is up to date, then merges that code into current branch

git push origin feature-x
git push -u origin feature-x (sets upstream?)

git pull vs git fetch?!
git fetch = updates local remote branch
git pull = merges

Remove input autocomplete colors

#app input:-webkit-autofill:disabled,
#app input:focus:-webkit-autofill:disabled {
box-shadow: 0 0 0 1em #e5e7eb inset;
-webkit-text-fill-color: #4b5563;
}

Query to get customers and the items from their last purchase

Let's say you want to get a list of all customers in your database and also include all items from their last purchase. How would you do it?

Here's one way to do it using a sub query.

SELECT customers.firstname, customers.lastname, purchases.createdat, items.name
FROM customers
INNER JOIN
(
  SELECT customerid, MAX(createdat) AS createdat
  FROM purchases
  GROUP BY customerid
) AS lastpurchase ON customers.id = lastpurchase.customerid
INNER JOIN purchases ON lastpurchase.customerid = purchases.customerid AND lastpurchase.createdat = purchases.createdat
INNER JOIN items ON purchases.id = items.purchaseid

I like this solution because the query is easy to read and understand. However, there are other ways to accomplish the same thing. One improvement was suggested by Jacob Bogers on Twitter. You can speed up the query by replacing the sub query portion of the query above with the following SQL instead.

SELECT customerid, createdat
FROM purchases a
WHERE NOT EXISTS
(
  SELECT 1
  FROM purchases b
  WHERE a.customerid = b.customerid AND a.createdat < b.createdat
)

Some other considerations.

  • If you want to include customers without purchases, just replace all INNER JOIN statements with LEFT OUTER JOIN statements instead.
  • If you use auto-incrementing primary keys in the purchases table you can call MAX on that column instead of the createdat column.

Syncing a forked repository with Git

Something I run into every now and then is that I'll fork a project repository, fix a bug and submit a pull request. Some time goes by and I want to contribute something else to the project. My fork is now most likely out of date and I need to sync it with the project repository again.

In technical terms, what you need to do is configure a "remote" that points to the "upstream" repository. You can check the currently configured remote repository for your fork using this command:

git remote -v

Look for the "upstream" entries. If nothing is listed as "upstream", go ahead and configure a new remote upstream repository like this:

git remote add upstream https://github.com/OWNER/REPOSITORY.git

Once you've configured your remote you can do the following (from the master branch in your fork) every time you need to sync:

git fetch upstream (fetches code from the remote repository).
git merge upstream/master (merges the changes from upstream/master into your local master branch).

Done!

Conditional event handler in Vue

Bit of a quick entry to write down how to conditionally add an event handler in Vue that passes a custom argument to its handler function (in addition to the event itself).

Here's the template code.

<input v-on="active ? { input: event => handle('Test', event) } : {}" />

Here's the relevant JavaScript.

data: {
  active: true,
},
methods: {
  handle(str, event) {
    console.log(str);
    console.log(event.target.value);
  }
}

Open example page.

CSS highlight effect to indicate important text

Here's a nice little yellow marker / highlight / glow effect that you can use to make text stand out on the page. It's achieved by layering text shadows on top of each other.

:root {
  --highlight: rgba(255, 193, 7, 0.5);
}

strong {
  font-weight: 500;
  text-shadow:
    -0.5em 0 1em var(--highlight),
    0.5em 0 1em var(--highlight),
    1em 0 1em var(--highlight),
    -1em 0 1em var(--highlight);
}

You can tweak the shadows to make the effect more similar to a yellow marker by decreasing the blur radius (the third value in all the shadows) and increasing the opacity of the color.

As a small side note, I've chosen to use the strong element for how I use this effect on my blog. I feel this is the most semantically correct HTML element to use since my intention is to indicate that a portion of the text is more important than the rest of it.

I've noticed that it's quite common to use the mark element for this sort of thing as well. However, I'm not sure that it's the most semantically correct HTML element to use in this case. The HTML specification says that "when used in the main prose of a document, it indicates a part of the document that has been highlighted due to its likely relevance to the user's current activity". There's no relevance to what the user is doing in this case, which is why I prefer the strong element instead.

If you are marking something up that has to do with the user's activity, it would be totally fine to use the mark element though. One common use case of that is when the user is doing a search and you want to highlight the text the user searched for within the result text.

Fade in Google font on load

It's been bothering me for a while now that when you load this blog you see a brief display of a fallback font before it gets replaced with the custom one (the Inter font family in my case). So, I decided to do something about it. Here's what I did.

First of all, I now tell the browser to not show a fallback font while downloading the custom one by setting font-display to block (technically this will render an invisible fallback font).

Since Google is supplying the CSS for the font we need to tell Google to use this setting though. This is done by passing the URL parameter display=block instead of the usual display=swap.

<link href="https://fonts.googleapis.com/css2?family=Inter&display=block" rel="stylesheet" />

Secondly, we can make the user experience a little nicer by fading in the custom font once it's been loaded by the browser. This is done by using the CSS Font Loading API.

The gist of it is that I set the main element to have an opacity of 0 to begin with. Then I use the Font Loading API to detect when the font has been loaded and start a CSS animation to set the opacity to 1. You can get the full details of this by viewing the source code of the example page.

Some notes to keep in mind if you decide to do something like this yourself:

  • By not showing a fallback font you're making the site less accessible. For people on slow connections it will take more time before they can start reading your website.
  • If you want to support older browsers you need to use a library that can detect when a web font has been loaded (such as Font Face Observer for example).
  • I chose to fade in the entire main element but in some cases it may make more sense for you to fade in the body element, or just the text itself.

Debouncing the window resize event

Just a quick tip if you want to run some code on window resize but want to avoid freezing / slowing down the user's browser. The code below will run doSomething after the user is "done" resizing the window (in this case 500ms after the last resize event).

window.addEventListener('resize', () => {
  window.clearTimeout(window.resizeTimeout)
  window.resizeTimeout = window.setTimeout(() => {
    doSomething(this.someVariable)
  }, 500)
})

Lining up menu items horizontally

Here's the semantic HTML that's typically used to create a menu:

<nav>
  <ul>
    <li><a href="/">Menu Item 1</a></li>
    <li><a href="/">Menu Item 2</a></li>
    <li><a href="/">Menu Item 3</a></li>
  </ul>
</nav>

There are (at least) two ways to get these menu items to line up next to each other on the page (horizontally instead of vertically that is). Either you can use display: inline-block on the li elements, or you can use display: flex on the ul container element.

You'll often see the inline block approach in older code bases and the more modern flexbox approach in more recent code.

One issue with the inline block approach is that you'll have to deal with the spaces that show up between the elements, at least if you're trying to achieve a pixel perfect design. It can be solved by setting font-size: 0 on the container element (i.e. the ul) and then resetting the font size on the individual list elements.

You don't have this problem with the flexbox approach, but you do have to get rid of the browser's default list styling by setting list-style: none on the ul element.

You can check out the code in more detail on the example pages here:

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.