svelte – closingtags </>
Categories
CSS HTML5 Javascript Svelte SvelteKit

Custom “Click Outside” Event with Svelte & TypeScript

Learn how to create a custom event handler for #Svelte components written in #TypeScript/#ts that listens for clicks outside of itself 🤯

Categories
Javascript Svelte SvelteKit

2023 Recap

Another busy year writing books and building apps!

Categories
Javascript Programming Svelte SvelteKit

Enabling Persistent Storage in IndexedDB

Exploring how to mark #IndexedDB storage as persistent for an offline first #PWA built with #SvelteKit

Categories
CSS HTML5 Javascript Svelte SvelteKit

I Wrote a (Technical) Book and So Can You

I was approached to write a book about #SvelteKit because I had written about it here. This post outlines my experience writing a technical book, from start to finish.

Categories
CSS Javascript Svelte SvelteKit

SvelteKit Up and Running is available now!

Today is the day! You can now buy my new book, SvelteKit Up and Running.

Visit https://sveltekitbook.dev to get your copy!

#svelte #SvelteKit #book

Categories
Javascript Svelte SvelteKit

10 Days to Publication!

Pre-order #SvelteKit Up and Running at https://packt.link/bVAyJ!

#svelte #sveltekit #packt #webdev #javascript

Categories
CSS Javascript Programming Svelte SvelteKit

Tailwind CSS + SvelteKit = ❤️ && 💔

In an effort to promote my upcoming book, I created the website https://sveltekitbook.dev. It has all of the information one may want to know before purchasing a copy. Of course, the book is about #SvelteKit so I had to build the site using #SvelteKit. I decided to take the opportunity to learn the latest technology web developers can’t shut up about; #TailwindCSS.

Categories
Javascript Programming Svelte SvelteKit

Why I Quit Writing

In my last post, I prefaced it by saying that I haven’t been posting here often because I’ve been busy. In reality, I only missed a single post back in May so I guess you could say that I haven’t actually quit writing.
(But you’ve already clicked the link and are reading the post so the bait worked, right? Might as well stick around for the rest.) 😎
In fact, I’ve doubled down on the writing because the latest project that has been keeping me so incredibly busy has been…
…writing my first book!
SvelteKit Up and Running
That’s right! An entire book made out of paper and ink like they used to have back in the ye olden days. Of course, there will be digital copies for you technology enthusiasts as well. The book is titled SvelteKit Up and Running: Building High Performance Web Apps with a Next Generation Framework. It is obviously about SvelteKit, the highly performant web application meta framework from the creators of Svelte. I wrote it because I wanted to give others the resource I wish I had when I was learning how to build web apps with Svelte and SvelteKit.
Whether you’re new to JavaScript development and are wondering which framework to pick up first, or are a seasoned developer tired of the bloat that comes with other popular frameworks; this book will teach you the concepts behind the project leading the field in revolutionizing JavaScript development. The examples found throughout the book are concise, practical, and easy to follow along with while still effectively demonstrating how to leverage the various techniques necessary for implementing features commonly found in web applications.
Pre-order

As of this writing, the book is currently trending #3 on Amazon! How wild is that?

If you’re interested in getting a copy (and I hope you are), it’s available for pre-order now at https://packt.link/bVAyJ! The book is scheduled for release on July 28th, 2023.
Want more?
If you’re curious about what it takes to write a book, the process behind it, how I got started, and lessons I learned, then be sure to subscribe to this site’s RSS feed, follow me on Telegram, or find me in the Fediverse. I’ll be posting more updates as well as outlining my experience throughout the entirety of this project!

Categories
CSS HTML5 Javascript Svelte

Svelte Work:Rest Timer

I was recently approached by a friend to build an app. Now, I’ve been cornered and pitched far too many half-brained apps in my day but it is refreshing to hear a good pitch. This pitch was for a timer, but not just any timer or stopwatch app that comes preinstalled on your phone. Rather, it was a workout timer that would also time your rests. As someone who often takes too long to rest between sets, I thought this sounded like a decent idea. I was told “nothing like this exists!” but a quick search of the web proves that false; it’s definitely been done. But I could do it as a Svelte component! And it’d be a fun coding challenge!
The Setup
If you’ve read any recent posts of mine, you know I’ve been working with SvelteKit lately. To get started with SvelteKit, I ran a few commands in the project directory:
npm create svelte@latest timer
cd timer
npm install
npm run dev
From there, I created the file /src/routes/+page.svelte as the entry point page for the app. I also created the file /src/lib/timer.svelte which is where all of the magic happens and included it in the aforementioned +page.svelte like so:
+page.svelte
<script>
import Timer from ‘$lib/timer.svelte’;
</script>

<div class=’content’>
<Timer />
</div>

<style>
div.content {
text-align: center;
width: 100%;
background-color: #130d0d;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
color: #d9d9d7;
}
</style>
$lib is an alias to /src/lib/ that is supported by SvelteKit out of the box.
The Markup
Most timers have a display of the running time, a start button, a stop button, and reset button so I went ahead and included those right away in /src/lib/timer.svelte. I also gave it some rough styling so it wasn’t awful to look at while testing. The basic structure of the component looks something like this:
<script>…</script>

<h1>Work+Rest Timer</h1>

<div class=’time’>{hr}:{min}:{s}.{ms}</div>

<div class=’controls’>
<button class=’start’ on:click={start}>{running ? `rest` : `start`}</button>
<button class=’stop’ on:click={stop}>stop</button>
<button class=’reset’ on:click={reset}>reset</button>
</div>

<style>
h1 {
text-align: center;
}
.time {
margin: 1.5rem;
font-size: 4rem;
}
.controls {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr;
}
button {
padding: 1.1rem 1.4rem;
margin: 1.1rem;
font-size: 1.5rem;
border: none;
background-color: #496bf0;
color: white;
transition: all .4s ease-in-out;
cursor: pointer;
grid-column: 3 / 4;
grid-row: 2 / 3;
}
button:hover {
background-color: #2047df;
}
button.stop {
background-color: #df3d2c;
grid-column: 2 / 3;
grid-row: 2 / 3;
}
button.stop:hover {
background-color: #a81303;
}
button.start {
background-color: #408e2a;
grid-column: 2 / 4;
grid-row: 1 / 2;
}
button.start:hover {
background-color: #245e14;
}
</style>

Yup, that’s a timer alright.

Eagle eyed readers likely noticed that there are some variables used in the markup that were not declared in the script tag. Lets get to that part now.
The Logic
This timer needs to be able to start, stop, reset, and display the running time. It also needs a “rest” feature which counts down to zero from whenever the “rest” button was clicked. To prevent users from starting another timer, I decided to turn the start button into the rest button while the timer is running. A quick rundown of how this ought to work:

clicking “start” starts the timer (duh)
the time since start is displayed in hours, minutes, seconds, and milliseconds
after clicking “start,” the “start” button becomes the “rest” button
clicking the “rest” button will reverse the time, counting down to zero
when the timer reaches zero, it will start counting up again until stopped or until “rest” is clicked again
clicking “stop” will stop the time
clicking reset will set hours, minutes, seconds, and milliseconds back to zero

That all seems fairly straightforward, right? Oh, but dear reader, you don’t know this (how could you have?) but when it comes to code, I hate programming anything related to time. And I’m really bad at math.
So why did I think this was a good idea? 🤔 Who knows? Maybe I just hate myself.

Side note: When working with computers and time, there’s all sorts of problems that can arise. It’s a lot easier to do math with time when you convert time to a regular number.

About those missing variables…
<script>
let interval = null;
let running = false;
let elapsed = 0;
let oldElapsed = 0;

$: ms = pad3(0);
$: s = pad2(0);
$: min = pad2(0);
$: hr = pad2(0);

// pad2/3 exist to format the numbers with leading zeros
const pad2 = (number) => `00${number}`.slice(-2);
const pad3 = (number) => `000${number}`.slice(-3);

const start = () => {

}
const stop = () => {

}
const reset = () => {

}
</script>
You’ll notice some that weren’t mentioned the markup; you can ignore those for now. The times (hr, min, s, ms) are all declared as reactive because I want the timer display to update as the those values change. Now all of this keeps the component from throwing errors but it doesn’t do anything. To make it do the things, let’s look at start() first.
const start = () => {
if(!running) {
countUp();
}
else {
countDown();
}
}
There! That was easy. Lets move on to the stop() and reset() functions:
const stop = () => {
clearInterval(interval);
oldElapsed = elapsed;
}
const reset = () => {
s = min = hr = pad2(0);
ms = pad3(0);
elapsed = oldElapsed = 0;
running = false;
clearInterval(interval);
}
Wait a minute, this doesn’t make any sense without looking at what’s actually going on in start(). Let’s back up. Here’s countUp() which is called by start() when running is false.
const countUp = () => {
let startTime = Date.now();
running = true;

interval = setInterval(() => {
elapsed = Date.now() – startTime + oldElapsed;

ms = pad3(elapsed);
s = pad2(Math.floor(elapsed / 1000) % 60);
min = pad2(Math.floor(elapsed / 60000) % 60);
hr = pad2(Math.floor(elapsed / 3600000) % 60);
});
}
The secret sauce here is setInterval(). Since it has not been provided the optional second argument, it loops on the function passed to it every millisecond. In that loop, the time since the counter began counting upwards is calculated by subtracting the start time from the current time. Then, each “section” of the timer display is calculated based on that elapsed time (and padded with extra leading zeros). Since the interval is assigned to the variable interval, I can clear it later on in stop() and reset() to stop the code.
Now for the part that I spent way too long on; countDown().
const countDown = () => {
stop();
const end = Date.now() + elapsed;

interval = setInterval(() => {
elapsed = end – Date.now();

ms = pad3(elapsed);
s = pad2(Math.floor(elapsed / 1000) % 60);
min = pad2(Math.floor(elapsed / 60000) % 60);
hr = pad2(Math.floor(elapsed / 3600000) % 60);

if(elapsed <= 0) {
clearInterval(interval);
reset();
countUp();
}
});
}
The first thing I want to do here is stop the timer where it’s at. That way, I can clear the value inside the interval variable. Once that’s done, the end time of the new timer needs to be calculated. That’s done by taking the amount of time elapsed and adding it to the current time. Once the end time has been calculated, the loop that runs every millisecond begins, where the amount of time elapsed is recalculated by subtracting the current time from the end time. It is then all shown in the timer display, and when the amount of elapsed time reaches zero, the interval is cleared, values are all reset to zero, and the count back up begins again.
If you were confused during any of that, that’s fine. You’re probably a sane, well adjusted human being that gets along perfectly well in social interactions. Congratulations 🥳!
All Together Now!
<script>
let interval = null;
let running = false;
let elapsed = 0;
let oldElapsed = 0;

$: ms = pad3(0);
$: s = pad2(0);
$: min = pad2(0);
$: hr = pad2(0);

const pad2 = (number) => `00${number}`.slice(-2);
const pad3 = (number) => `000${number}`.slice(-3);

const countUp = () => {
let startTime = Date.now();
running = true;

interval = setInterval(() => {
elapsed = Date.now() – startTime + oldElapsed;

ms = pad3(elapsed);
s = pad2(Math.floor(elapsed / 1000) % 60);
min = pad2(Math.floor(elapsed / 60000) % 60);
hr = pad2(Math.floor(elapsed / 3600000) % 24);
});
}

const countDown = () => {
stop();
const end = Date.now() + elapsed;

interval = setInterval(() => {
elapsed = end – Date.now();

ms = pad3(elapsed);
s = pad2(Math.floor(elapsed / 1000) % 60);
min = pad2(Math.floor(elapsed / 60000) % 60);
hr = pad2(Math.floor(elapsed / 3600000) % 24);

if(elapsed <= 0) {
clearInterval(interval);
reset();
countUp();
}
});
}

const start = () => {
if(!running) {
countUp();
}
else {
countDown();
}
}
const stop = () => {
clearInterval(interval);
oldElapsed = elapsed;
}
const reset = () => {
s = min = hr = pad2(0);
ms = pad3(0);
elapsed = oldElapsed = 0;
running = false;
clearInterval(interval);
}
</script>
I put together a repository of this code so you can read it in all of its glory there. If you follow the development, you’ll see new features like “beeps”, fun colors, and buttons that allow you to multiply the rest time by a factor of 1.5, 2, or even 3! If you like tracking calories as well as working out, then keep a watchful eye out for this new feature coming to your favorite health tracking application.

Categories
Javascript Programming Svelte

Svelte stores in IndexedDB

I know that I just wrote a post about using Svelte Stores with localStorage but very shortly after writing that (and implementing it in my own app 🤦), I came across this blog post from Paul Maneesilasan which explained the benefits of using IndexedDB:

So, the primary benefits to using a datastore like indexedDB are: larger data storage limits (50MB), non-blocking operations, and the ability to do db operations beyond simple read/writes. In my case, the first two alone are enough to switch over.
https://www.paultman.com/from-localstorage-to-indexeddb/
Since I had some fresh ideas for my own app about storing lots of data that could potentially would definitely go over the Web Storage 5-10MB limit, I dug even deeper into the research. In the end, I too decided to make the switch.
Sexy Dexie
The documentation for IndexedDB can be overwhelming. It’s great how thorough it is, but I found it difficult to read. After spending a few hours trying to understand the basics, getting bored, looking at memes, and finally remembering what I was supposed to be doing, I came across a package called Dexie. Dexie is a wrapper for IndexedDB, which means that it will make calls to the database for me; for instance Table.add(item) will insert the item object on the specified table. This greatly reduced my need to build that functionality myself. Since I hate reinventing the wheel (and my brain can only learn so many things in a day), I opted to use this package to manage the database. Plus, Dexie has a handy tutorial for integrating with Svelte and makes managing database versions a breeze.
While the Dexie + Svelte tutorial is great, including Dexie in each and every component that gets and sets data (like the tutorial does) would be cumbersome. It would be far simpler to use Svelte stores across components, and have the stores talk to IndexedDB via Dexie.
Custom Stores
Since IndexedDB; and therefore Dexie, utilize asynchronous APIs, a few things need to be done differently to integrate with Svelte stores. Firstly, we can’t just “get the values” from the database and assign them to a writable store (like we did in the localStorage example) since we’ll be receiving a Promise from Dexie. Secondly, when the data is updated in the store, we also need to update the data in the database; asynchronously.
This is where custom stores come in. In a custom store, we can create our very own methods to manage getting and setting data. The catch is that the store must implement a subscribe() method. If it needs to be a writable store, then it also needs to implement a set() method. The set() method will be where the magic happens.
To get started, follow Dexie’s installation instructions (npm install dexie) and create a db.js file to initialize the database. I’ve added a couple methods to mine to keep database functionality organized:
Note that is generic code sampled from helth app so references to water, calories, and sodium are done in the context of a health tracker.
import { Dexie } from ‘dexie’;
// If using SvelteKit, you’ll need to be
// certain your code is only running in the browser
import { browser } from ‘$app/environment’;

export const db = new Dexie(‘helthdb’);

db.version(1).stores({
journal: ‘date, water, calories, protein, sodium’,
settings: ‘name, value’,
});

db.open().then((db) => {
// custom logic to initialize DB here
// insert default values
};

export const updateLatestDay = (date, changes) => {
if(browser) {
return db.journal.update(date, changes);
}
return {};
};

export const getLatestDay = () => {
if(browser) {
return db.journal.orderBy(‘date’).reverse().first();
}
return {};
};

export const updateItems = (tableName, items) => {
if(browser) {
// .table() allows specifying table name to perform operation on
return db.table(tableName).bulkPut(items);
}
return {};
}

export const getItems = (tableName) => {
// spread all of the settings records onto one object
// so the app can use a single store for all settings
// this table has limited entries
// would not recommend for use with large tables
if(browser) {
return db.table(tableName).toArray()
.then(data => data.reduce((prev, curr) => ({…prev, [curr.name]: curr}), []));
// credit to Jimmy Hogoboom for this fun reducer function
// https://github.com/jimmyhogoboom
}
return {};
}
I won’t go over this file because it’s self explanatory and yours will likely be very different. After you have a database, create a file for the stores (stores.js).
import * as dbfun from ‘$stores/db’;
import { writable } from ‘svelte/store’;

// sourced from https://stackoverflow.com/q/69500584/759563
function createTodayStore() {

const store = writable({});

return {
…store,
init: async () => {
const latestDay = dbfun.getLatestDay();
latestDay.then(day => {
store.set(day);
})
return latestDay;
},
set: async (newVal) => {
dbfun.getLatestDay()
.then(day => {
dbfun.updateLatestDay(day.date, newVal);
});
store.set(newVal);
}
}
}

function createNameValueStore(tableName) {
const store = writable({});

return {
…store,
init: async () => {
const items = dbfun.getItems(tableName);
items.then(values => {
store.set(values);
})
return items;
},
set: async (newVal) => {
dbfun.updateItems(tableName, Object.keys(newVal).map((key) => {
return {name: key, value: newVal[key].value}
}));
store.set(newVal);
}
}
}
export const today = createTodayStore();
export const settings = createNameValueStore(‘settings’);
Here’s break down what this does:

Import the db.js file and any extra functionality added to it.
Import { writable } from svelte/store since we’ve already established that I hate reinventing the wheel.
Create 2 functions; createTodayStore() and createNameValueStore(). Both functions are mostly identical except for the logic they call on the database.
Each function then creates their own store from writable with an empty object as the default value.
Both functions return an object that adheres to the store contract of Svelte by implementing all the functionality of writable, overriding set(), and adding a custom method init().
init() queries the database asynchronously, sets the value of the store accordingly, then returns the Promise received from Dexie.
set() updates the data within the database given to the store and then proceeds to set the store accordingly.
Export the stores.

Accessing the stores
Now that the stores have been created, accessing them isn’t quite as simple as it was when they were synchronous. With a completely synchronous store, svelte allowed access to the data via the $ operator. The stores from localStorage could be accessed or bound simply by dropping $storeName wherever that data was needed. With the new asynchronous stores, calling the custom init()method is mandatory before accessing the data; otherwise, the store won’t have any data! Here’s a simple Svelte component showing how to access the settings store data in the <script> tag and binding to the today store in the markup.
<script>
import { onMount, afterUpdate } from ‘svelte’;
import { today, settings } from ‘$stores/stores’;
import Spinner from ‘$components/Spinner.svelte’;

$: reactiveString = ‘loading…’;

onMount(() => {
settings.init()
.then(() => {
if(‘water’ in $settings) {
reactiveString = `water set to ${settings.water.value}`;
}
});
</script>

{#await today.init()}
<Spinner />
{:then}
<input type=”number” bind:value={$today.water} />
{:catch error}
<p>error</p>
{/await}
<p>{reactiveString}</p>

<Spinner /> source can be found here.
This instance of a reactive variable doesn’t make much sense unless it were tied to another value but the logic remains the same.
Accessing the today store value in the markup is made possible by use of Svelte’s {#await}.

el fin
Is this the best way connect Svelte stores to IndexedDB?
I have no idea. Probably not.
But it does seem to work for me. If you’ve got ideas on how it could be improved, leave a comment. I’m always open to constructive criticism. If you’re curious about the app this is taken from, I’ve previously written about it here. The source code for it can be found on GitHub.
And lastly, if you’ve enjoyed this post or found value in it, consider sponsoring me on GitHub.