07 Feb 2018, 19:00

Messaging Between Tabs Using Service Worker

As of late I’ve been thinking a lot about Service Workers (sorry if this is getting boring!), predominantly in relation to the Cache API. For those of you who aren’t familiar, Service Workers are a type of Web Worker that are shared between a domain scope, and can do cool things like intercept network requests and cache them. This is powerful because it means you can improve the performance of your app for commonly accessed assets and even go offline (as the Service Worker acts a network proxy). Alongside caching, Service Workers provide a host for other capabilities for features such as Push Notifications and also syncing data in the background using the Background Sync API. Not too shabby eh?

In this post I want to think about something slightly different. As mentioned Service Workers have this interesting property in that each Service Worker is registered per scope (by default the base location of the Service Worker script). This means multiple ‘clients’ (a “document in a browser context” or more simply tabs and windows) share the same Service Worker.

One side effect of this is that these clients can pass messages to the Service Worker and then propagate down messages to other open clients. I wanted to explore the potential of this capability a little more. I did a bit of research and found this fantastic blog post from Craig Russell about sending messages with Service Workers. I want to expand on Craig’s work to take it a little bit futher into the realm of updating tab state. Under the assumption that we have correctly registered our Service Worker in the page, lets demonstrate how we might achieve basic message passing, and then see what kind of things that might allow us to do.

From the client code we need a function to allow us to post a message to a Service Worker. One misconception is that the data passed needs to be a string, but it can actually be any basic data type that is acceptable by the Structured Clone Algorithm. In short this is pretty much everything except Errors, Functions and DOM nodes. In theory if you needed to pass these things you could use JSON.stringify and JSON.parse but these present their own pitfalls. This aside let see how the message sending works:


function stateToServiceWorker(data){
    if (navigator.serviceWorker && navigator.serviceWorker.controller) {
        navigator.serviceWorker.controller.postMessage(data);
    }
}

So this function covers sending from the client, what about receiving from the client? We could do something like this in our registration code to register for messages from our Service Worker:


if ('serviceWorker' in navigator) {

    navigator.serviceWorker.register('service-worker.js')
        .then(function() {
            return navigator.serviceWorker.ready;
        })
        .then(function(reg) {
            
            // Here we add the event listener for receiving messages
            navigator.serviceWorker.addEventListener('message', function(event){
                console.log(event.data)
            });

        }).catch(function(error) {
            console.error('Service Worker registration error : ', error);
        });

}

This concludes our client side code for sending and receiving messages. Now what about our Service Worker? Firstly lets examine receiving messages:


self.addEventListener('message', function(event){
    // Receive the data from the client
    var data = event.data;

    // The unique ID of the tab
    var clientId = event.source.id 

    // A function that handles the message
    self.syncTabState(data, clientId);
});

Now that we’ve received a message from the client, we need to know how to send it back to potential clients. We could probably inline this code but wanted to break it down for this demonstration:


self.sendTabState = function(client, data){
    // Post data to a specific client
    client.postMessage(data);
}

Greg’s post actually shows how you can send a message back to the client in question if you so wish. This can be done by sending a reference to a MessageChannels ports across and using some nice Promise callback wrapping, but for the sake of simplicity I’m omitting that here.

Now we can send some message to any specific client of our choosing, but how do actually call this function to access all the clients? We could do something like this:


self.syncTabState = function(data, clientId){
    clients.matchAll().then(function(clients) {

        // Loop over all available clients
        clients.forEach(function(client) {

            // No need to update the tab that 
            // sent the data
            if (client.id !== clientId) {
                self.sendTabState(client, data)
            }
           
        })
    })
}

So we’ve shown how to send and receive messages from the Service Worker. What’s the actual use case for this? Well the original idea I had in mind was quite abstract in syncing state across all opened tabs for an application. Let me show you a basic example through the medium of the this suboptimal gif:

Since then I’ve had a deeper think and I believe there might be some more exact/substantial use cases for this technique to consider, especially in the web app space. For example you could sync the state of your application across tabs without the explicit need for polling/websockets, or watching localStorage / IndexDB. Think updating a balance after bank transfer on another tab, or close a EU cookies banner simultaneously across open clients. You could also do things like triggering tabs that are on a specific route to perform some action, like open a specific dialog or hide information that is no longer relevant. Kitson Kelly made the point that this could come into it’s own in more heavy weight / power-user centered applications.

I’d be really opening to hearing other peoples suggestions on the matter, so feel free to drop me a line on Twitter. If you are interested in seeing the code you can check out the GitHub here.

24 Dec 2017, 14:53

Getting going with ES6 Maps

As we move into a world where modern browsers are supporting the nearly the entire ES6 specification, we have a variety of data structures to use for storing and accessing our data as native built-in primitives. In ES6 we specifically gain access to ‘keyed collections’. Keyed collections encompasses four built-ins, these are namely Set, Map, WeakSet and WeakMap. Here we’ll be talking about the Map in particular. Let’s get stuck in!

What is a Map?

A Map is a collection of key value pairs, where both key and values can be of any type. This differs from how one might have traditionally used a JavaScript Object as a map, as historically keys have been strings only (this has changed with the introduction of Symbols but that’s for a different blog post to come!).


const obj = { 1 : "one" }
typeof Object.keys(obj)[0] // Evaluates to string

So with Maps, we have the additional power of using anything we like as our key, even a HTMLElement, which is powerful because it makes it easy to associate data with a DOM node. To illustrate the use of none String keys, we can see this example using booleans:


const map = new Map([
    [true, "Things that are true"],
    [false, "Things that are false"]
]);

Here true and false are the respective keys in our collection. So how does JavaScript know how to evaluate something as matching a Map key? Well the approach is very similar to the === (identity) operator, with the minor caveat that NaN (Not a Number) equates to NaN even though NaN !== NaN.

How do we use it?

Maps provide a set of methods for interfacing with them such as get, set and delete. These methods provide a clean inteface for interacting with our data. Let’s see how that works in practice:


const one = "one";
const map = new Map([ ["one", 1] ])
map.get("one"); // Returns 1
map.get(one); // Returns 1

map.set("two", 2);
// Returns the Map with "two" set to 2

map.delete("one");
// Returns true if successful or false if unsuccessful

map.clear();
// Returns undefined

As you can see there’s a little gotcha here, get, set and delete all have different return behaviours. Get will return the associated value, set will return the Map itself, and delete will return a boolean, and clear returns undefined. Something to keep in mind!

We also have some really nice convenience methods to help us interface. For example, has which will tell us if a key exists in a Map:


const map = new Map([ ["one", 1], ["two", 2] ]);
map.has("two") // true

This is very useful for quickly checking the existence of key in a Map. What about if we want to iterate over a Maps contents? Firstly there are a few convenient methods in this regard: values, entries and forEach. Let’s look at values and keys. Both these methods return an iterable. Iterables are a little outside the scope of this post, but think of them as JavaScript variables that have defined behaviours for what happens when you iterate over them (for example using for…of):


// Entries
const map = new Map([ ["one", 1], ["two", 2] ]);

for (const num of map.entries()) {
    console.log(num); 
    // Logs ["one", 1]
    // Then ["two", 2]
}

// Values
for (const key of map.values()) {
    console.log(key);
    // Logs 1
    // Then 2
}

Alongside this we have the forEach method that some of you may be familiar with from the Array method of the same name. It behaves like so:


const map = new Map([ ["one", 1], ["two", 2] ]);
map.forEach((key, value) => console.log(key, value));
// Logs ["one", 1]
// Then ["two", 2]

Lastly we can quickly and conveniently get the size of a map with the size method. It is fairly straight forward and operates like so:

const pets = Map([ ["dog", "woof!"], ["cat", "meow"], ["goldfish", "bop bop bop"] ]);
animals.size(); // Evaluates to 3

Which would have been less elegant using an Object:

var pets = {"dog", "woof!", "cat": "meow", "goldfish": "bop bop bop"};
Object.keys(pets).length;

Why use a Map?

Simply put, the Map is more idiomatic and effective primitive than an Object for use as a mutable key-value pair data structure. We have the added flexibility of using anything we please as our keys, alongside a clear set of a powerful methods that we have shown above. These methods allow us to be more semantic and straightforward about interacting and manipulating our data. It also avoids some of the potential pitfalls of the Object, for example, because of the interface of a Map, we can avoid the possibility of colliding with default Object properties such as toString or hasOwnProperty. This is obviously an edge case but provides us with extra reassurance:


// With a traditional Object
const obj = {};
obj["toString"] = "woops";
console.log(obj.toString);
// Evaluates to woops

// With a Map
const map = Map();
map.get("toString") // undefined
map.set("toString", "This is fine");

Overall it expresses a lot of cool properties that suite it better to behaviours we wanted from using an Object as a Map previously. Happy Mapping!

03 Dec 2017, 10:10

Creating Gradient Borders with CSS

Products have seen a resurgence of gradients in the past year or so. I too wanted to cash in on the PowerPoint circa 2003 aesthetic and as such I took to work on finding how I could add some gradients to the button outlines on my blog. Unfortunately I really struggled to find any decent guides on the matter, but after a little bit of digging and understanding the solution was fairly straightforward. Recently I’ve been trying to write shorter blog posts more often about little things I’ve learned, so I thought I’d share that with you folks today!

So from my research, the canonical way to do gradient borders in modern browsers appears to be as such:


    .someButton {
        border-image: linear-gradient(to left, #743ad5 0%, #d53a9d 100%);
        border-image-slice: 1;
        border-width: 2px;
    }
   

This is the gradient I am currently using for the navigation buttons on the left. Here border-image allows a developer to pass in a image URL or a gradient (perhaps unintuitively). You can find it’s full details on MDN here. In our case we are interested in a gradient so we can use linear-gradient or alternatively a radial-gradient if you’re looking for that sort of effect. You can use a direction argument to specify a given direction for the gradient, for example in our case above to left or bottom right might be another example.

A nice little website for generating gradients is https://www.css-gradient.com/, where you can play around with different properties to get your desired result. A note however, you shouldn’t need a prefix any more for gradients so you can ignore that!

border-image-slice is the second CSS property we need to fill to get this working. As I was, you might be asking what on earth does this do? If you’re anything like me, it can be quite hard to wrap your head around. As an exact definition: “The border-image-slice CSS property divides the image specified by border-image-source in nine regions: the four corners, the four edges and the middle” (MDN). The property has less meaning in the gradient context as it specifies offsets for the image in question to position it for the border in question. border-image-slice can take up to four sets of arguments, each with different behaviours depending on the number of arguments provided. The arguments here can either be a percentage or a number, with the default value for the property being 100%. For our use case, we will be providing it a value of 1. If you want to get a clearer picture, consider seeing the docs for the property.

Finally, good old border-width will provide a given width for our border in question. You can adjust it as you see fit to get the desired effect. Here’s some little gradient buttons I made for inspiration!



.buttonOne {
    border-image: linear-gradient(to right, #b861c6 0%,#ffc7c7 100%);
    border-image-slice: 1;
    border-width: 3px;
}

.buttonTwo {
    border-image: linear-gradient(to left, #61c69d 0%, #2d72bc 100%);
    border-image-slice: 1;
    border-width: 3px;
}

.buttonTwo {
    border-image: linear-gradient(to left, #61c69d 0%, #2d72bc 100%);
    border-image-slice: 1;
    border-width: 3px;
}

Which after applying the class to your button in question would look a little something like this in your browser:

Hopefully that’s been a fairly painless guide to generating your own gradient borders in CSS. Have fun!