Testing with Dojo

dojo testing

When you use the Dojo cli to create an application, it comes with a full test framework for you to use. If like me, you aren’t as diligent with your tests as you maybe you could be, that’s ok, there’s no testing police that will give you a citation, only your own shame when you try to track down how you broke some feature after some updates you made. I kid, I kid. But, it is a good idea to have some tests written to make sure core features of your application don’t break as you write new code.

When you first create a Dojo application, it will already provide you with some unit tests that you can use as a guide.

Default Dojo Installation

You can find this sample repo on github.

Let’s see what a unit test looks like.

Running Tests

You can run these tests using npm test and you should get results like below.

Dojo testing results

So what does one of these tests look like?

// tests/unit/widgets/Profile.ts
const { describe, it } = intern.getInterface("bdd");
import harness from "@dojo/framework/testing/harness";
import { w, v } from "@dojo/framework/widget-core/d";

import Profile from "../../../src/widgets/Profile";
import * as css from "../../../src/widgets/styles/Profile.m.css";

describe("Profile", () => {
  it("default renders correctly", () => {
    const h = harness(() => w(Profile, { username: "Dojo User" }));
    h.expect(() => v("h1", { classes: [css.root] }, ["Welcome Dojo User!"]));
  });
});

When testing widgets, you are basically testing that the output of the widget is what you expect, especially when given a set of properties. Ideally, rendering a widget is going to be pure function, meaning they should be pretty easy to test when given the same input.

The Dojo framework comes with a harness helper, which can be used to test your widgets. You can give it a widget, and check that the virtual DOM output is what you expect.

  • Does it render as expected?
  • Does a child widget or element render as expected?
  • Do event handlers work as expected?

Assertion Templates

The Profile widget we tested above has a property username that we can test against in the output. We could rewrite the entirety of the expected virtual DOM output for each test (that’s a lot of typing) or we could create an assertion to test against that would allow us to change the expected properties on each run.

Let’s see how that would work.

First, I need to update the Profile widget slightly since the username property is required. We can make it optional and provide a default value in the widget.

// src/widgets/Profile.ts
export interface ProfileProperties {
  username?: string;
}

export default class Profile extends WidgetBase<ProfileProperties> {
  protected render() {
    const { username } = this.properties;
    return v("h1", { classes: [css.root] }, [
      `Welcome ${username || "Stranger"}!`
    ]);
  }
}

This is a little safer anyway. Now in my test, I can create my assertion template.

// tests/unit/widgets/Profile.ts
// Add the assertionTemplate module
import assertionTemplate from "@dojo/framework/testing/assertionTemplate";
...

// Create my assertion
const profileAssertion = assertionTemplate(() =>
  v("h1", { classes: [css.root], "~key": "welcome" }, ["Welcome Stranger!"])
);

describe("Profile", () => {
  it("default renders correctly", () => {
    const h = harness(() => w(Profile, {}));
    // Test against my base assertion
    h.expect(profileAssertion);
  });
});

We can test against our base assertion like we did before. In our assertion template, we add a ~key property to the node so that we can update its expected output. In a tsx file, this is called assertion-key.

We can now test the output if we provide a given property to the widget.

// src/tests/unit/widgets/Profile.ts
describe("Profile", () => {
  it("default renders correctly", () => {
    const h = harness(() => w(Profile, {}));
    h.expect(profileAssertion);
  });

  it("renders given username correctly", () => {
    // update the expected result with a given username
    const namedAssertion = profileAssertion.setChildren("~welcome", [
      "Welcome Kel Varnsen!"
    ]);
    const h = harness(() => w(Profile, { username: "Kel Varnsen" }));
    h.expect(namedAssertion);
  });
});

What the ~key allows is for us to update that expected portion of our assertion template. So if I provide a username, I should expect a different welcome message. The assertionTemplate.setChildren() returns a new assertion template you can reference so that you don’t need to reset it after each unit test, which is incredibly useful and I think is a nice touch to the library.

You can read more about assertion templates and some of its other useful methods in the documentation.

Summary

This was just a quick look at testing with Dojo, but I think it highlights how useful the provided tools are for you test your widgets! Dojo tests use intern by default, so you can look at the docs on how to test the business logic of your applications as well. An added benefit here is that intern provides functional tests, so you can test the behavior of your application as a user would interact with it. This would require a blog post of its own, but you can look at the Dojo todo-mvc example to see how it uses functional tests.

Now I know everyone is going to go out and write unit tests for all their code!

Dojo from the Blocks

Dojo blocks

One of the low-key features that was released in Dojo 5 was the introduction of Blocks. Blocks go hand-in-hand with Dojo build time rendering.

What Blocks allow you to do is run some arbitrary code in a node environment during the build process.

Build time rendering is a great tool you can use to generate static content without having to worry about any server side component to generate pages as requested.

For example, you could use Blocks to preprocess images that you might want loaded into your page, or maybe a more common use case of converting markdown to use for you blog or site. Blocks give you the flexibility to run code you might normally run in the server environment during your build process.

Building a block

Maybe I want to build my blog on top of Dojo, and I want to just write my articles in markdown. I can use a library like showdown to parse my markdown files to HTML. Here is a very basic module that can do this.

// src/blocks/markdown.block.ts
import * as fs from 'fs';
import { resolve } from 'path';

import { Converter } from 'showdown';

const mdConverter = new Converter();

export default function (path: string) {
  path = resolve(__dirname, path);
  const file = fs.readFileSync(path, 'utf8');
  // convert Markdown to HTML
  const html = mdConverter.makeHtml(file);
  return html
};

Blocks are types of metas you can use in your widgets. I can use my block by calling the meta, and running it with with the needed arguments, like the path to the markdown file I want to parse.

import WidgetBase from "@dojo/framework/widget-core/WidgetBase";
import { dom } from "@dojo/framework/widget-core/d";
import Block from "@dojo/framework/widget-core/meta/Block";
import { tsx } from "@dojo/framework/widget-core/tsx";

import fromMarkdown from "../blocks/markdown.block";

import * as css from "./styles/About.m.css";

export default class About extends WidgetBase {
  protected render() {
    const node = document.createElement("div");
    // Use my block
    const message = this.meta(Block).run(fromMarkdown)(
      "../../markdown/post2.md"
    );
    node.innerHTML = message;
    // Create a vnode to inject my HTML
    const vnode = dom({ node });
    return (
      <div>
        <h1 classes={css.root}>About Page</h1>
        {vnode}
      </div>
    );
  }
}

I can now naively inject my parsed markdown as HTML into my page. Ideally, I would like to convert that HTML into real virtual dom nodes, but I haven’t gotten that far yet.

You can quickly see how useful this would be during build time to process files, maybe pull in some external files and use them in an app.

Image processing

In my app, I might have some images that I want to convert to base64 strings so I can embed them. I can use a tool like sharp to resize my images. When I do, I can go ahead create the virtual dom nodes and return them in my block.

// src/blocks/imagebase64.block.ts
import { resolve } from 'path';
import { v } from '@dojo/framework/widget-core/d';
import * as sharp from 'sharp';

export default async function (path: string) {
  path = resolve(__dirname, path);
  // resize my images
  const images = [
    await sharp(path).resize(200).toBuffer(),
    await sharp(path).resize(300).toBuffer(),
    await sharp(path).resize(400).toBuffer(),
    await sharp(path).resize(500).toBuffer()
  ];

  return images.map((a) =>
    v('img', { src: `data:image/jpeg;base64, ${a.toString('base64')}`, alt: 'sally' })
  );
};

You might notice, that I’m able to run asynchronous tasks inside my block. This allows me to do some more interesting things like image processing, fetching data, or maybe run some sort of analysis on a dataset to create formatted json that can be used by a charting library! I’m just throwing out some ideas here!

Summary

You can view the source code for this sample here, and you can view a live demo here.

Dojo Blocks are really interesting, and I think they provide a whole new level of functionality for developers taking advantage of build time rendering with Dojo. I don’t see a reason not to use build time rendering, and Blocks offer you a whole new opportunity to get crazy about it. I’m currently looking at a rewrite of my blog with Dojo using them!

Maintain State with Dojo Stores

Dojo Stores and Processes

We previously looked at how you could maintain state with Dojo containers using a context for your application. To recap, containers are basically higher order components that you can use to add state management to widgets.

Using a context with containers makes the process fairly simple, however if you want to wire up multiple containers and routes with shared state, you might want to start looking at using Dojo Stores.

Dojo Stores work by using Processes to execute Commands with varying Operations against your state. You don’t need to define your operations, Dojo provides them for you. These operations are based on the JSON Patch format. They currently support add, remove, replace, and test. If you’re not familiar with these operations or they look intimidating, don’t worry, they are much simpler than they may seem at first.

In order to try and grasp how everything works, let’s create a small application that explores the Star Wars API.

API Explorer Widget

First thing we’ll do is create a widget that can explore the API. I’ll use a Listbox to display the endpoints of the API and a TextArea to display the raw JSON results of the selected endpoint.

// src/widgets/APIExplorer.tsx
import { tsx } from "@dojo/framework/widget-core/tsx";
import { WidgetBase } from "@dojo/framework/widget-core/WidgetBase";
import { watch } from "@dojo/framework/widget-core/decorators/watch";
import Listbox from "@dojo/widgets/listbox";
import TextArea from "@dojo/widgets/text-area";
import theme from "@dojo/themes/dojo";
import * as css from "./styles/APIExplorer.m.css";

import { ExplorerProperties, Item } from "../interfaces";

export class APIExplorer extends WidgetBase<ExplorerProperties> {
  @watch() index = 0;
  @watch() label = "";

  onAttach() {
    this.properties.fetchAllResults();
  }
  protected render() {
    return (
      <div classes={css.root}>
        <Listbox
          theme={theme}
          key="listbox1"
          activeIndex={this.index}
          widgetId="listbox1"
          optionData={this.properties.items}
          getOptionLabel={(option: Item) => option.label}
          getOptionSelected={(option: Item) => option.label === this.label}
          onActiveIndexChange={(index: number) => {
            this.index = index;
          }}
          onOptionSelect={(option: Item, index: number) => {
            this.label = option.label;
            this.properties.fetchData(option);
          }}
        />
        <TextArea rows={15} theme={theme} value={this.properties.result} />
      </div>
    );
  }
}

// src/interfaces.ts
export interface ExplorerProperties {
  items: Item[];
  result: string;
  fetchAllResults: () => void;
  fetchData: (item: Item) => void;
}

This widget has some local state to manage the selected value of the Listbox, but other than that, it relies on the properties passed to it in order to display anything useful. I’ll be using a container to pass those properties to this widget. But first, how would I make my queries and actually update my application state in the first place?

Processes

Processes are used to execute commands against your application store. It’s in the processes that you are going to be doing most of your heavy lifting for your application. This is where you can do queries, transformations, filtering, validation, and so on. There are factory functions in Dojo to help you create your commands.

// src/processes/starwarsProcesses.ts
import {
  createProcess,
  createCommandFactory
} from "@dojo/framework/stores/process";
import { replace } from "@dojo/framework/stores/state/operations";

import { State, Item } from "../interfaces";

// commandFactory typed to my application state
const commandFactory = createCommandFactory<State>();

In this application, I’m going to use the replace operation to make my state updates. If I were adding items to an array in my commands, I could do some interesting things using the at helper and add operation to insert new items into an array at specific indices.

Let me create a command that will fetch all the available endpoints of the Star Wars API.

// src/processes/starwarsProcesses.ts
const fetchItems = commandFactory<Item>(async ({ path }) => {
  const response = await fetch("https://swapi.co/api/");
  const json = await response.json();
  const items: Item[] = Object.keys(json).map(key => {
    return {
      label: key,
      value: json[key]
    };
  });
  return [replace(path("items"), items)];
});

I use my command factory to create a function that will make my query and then returns an array of operations. Why any array? Because I may want a single command do multiple state updates. In this case I use [replace(path(“items”), items)]. This may look odd at first, but the path method is a way to designate a property path in my state. If I want to use the value from my state I could use a get helper and write get(path(“items”)), which would return the items array.

I was unsure how I felt about this method of accessing state at first, but it lends itself very well to a functional and reactive method of interacting with state that has really grown on me. The behavior is very much like working with lenses.

Now, I’ll create a command to fetch the results of one of the selected endpoints.

// src/processes/starwarsProcesses.ts
const fetchResult = commandFactory<Item>(async ({ path, payload }) => {
  const response = await fetch(payload.value);
  const result = await response.json();
  return [replace(path("result"), JSON.stringify(result, undefined, 2))];
});

Very similar to the previous command I am going to fetch the results of an endpoint, but that endpoint URL is coming from a payload object that was passed to my command. I’ve typed this payload to have a value property, but it will be any argument that has been passed to the process that will execute this command. We’ll see how that is done in a moment, but first we need to create a couple of processes that will be used to execute these commands.

// src/processes/starwarsProcesses.ts
export const fetchItemsProcess = createProcess("fetch-items", [fetchItems]);
export const fetchResultProcess = createProcess("fetch-result", [fetchResult]);

We create two processes to do two distinct operations on my state. What I find interesting here is that I can have a single process execute multiple commands that could in turn perform multiple operations against my state. I haven’t done this yet, but I find the fact that I could to be awesome!

Container

Let’s wrap our widget in a container that will use our processes to wire up properties and methods.

// src/containers/APIExplorerContainer.ts
import { Store } from "@dojo/framework/stores/Store";
import { StoreContainer } from "@dojo/framework/stores/StoreInjector";
import { APIExplorer } from "../widgets/APIExplorer";

import { State } from "../interfaces";

import {
  fetchItemsProcess,
  fetchResultProcess
} from "../processes/starwarsProcesses";

function getProperties(store: Store<State>): any {
  const { get, path } = store;
  
  return {
    items: get(path("items")),
    result: get(path("result")),
    fetchAllResults: fetchItemsProcess(store),
    fetchData: fetchResultProcess(store)
  };
}
// Use a StoreContainer
export const APIExplorerContainer = StoreContainer(
  APIExplorer,
  "state",
  { getProperties }
);

We are using a specific container called a StoreContainer that will inject our store in to our getProperties method. This method is how you can pass properties to your wrapped widget from the container.

You can see here that the store has helper methods, get and path, that I mentioned before to access the values of properties on the store. I can now pass processes that will execute commands against the store as methods that my widget is expecting as part of its properties.

Once this is done, I need to register my store with my application and inject it.

// src/main.tsx
const store = new Store();
const registry = registerStoreInjector(store);

class App extends WidgetBase {
  protected render() {
    return <APIExplorerContainer />;
  }
}

const r = renderer(() => <App />);
r.mount({ registry });

I use a helper called registerStoreInjector and then inject that store into a named state that I used in my container.

What you end up with is an application like this one.

Summary

There is a lot happening here, but what it boils down to is the following steps.

  • Create processes to execute commands against a Store
  • Wrap widget in a container to pass processes and store properties
  • Inject store into application

Any subsequent updates the store will be passed down to the widget. There is even more you could do with your store. I could have wrapped my widget in a StoreProvider and passed store properties down, and manually subscribe to store changes. Subscribing to store changes could be a very useful tool inside your applications for some granular control of managing widget visibility for example.

Processes even provide the ability to add middleware so you can add logging and authentication, or maybe payload validation without having to pollute your processes with extra code and possibly extra sources of errors.

At the end of the day, I have had a fun time learning how to use processes and stores in my applications. They provide a lot of flexibility in managing state and I can already see how useful they would be in building out an application as it grows!

Be sure to subscribe to the newsletter and stay up to date with the latest content!

Style Dojo Widgets

dojo styling

There’s an entire Dojo tutorial on creating custom themes, and if you look at any of the guides on creating widgets you will learn how to create css modules to use with your widgets and your applications.

But how about if you want to use some of the out-of-the-box widgets with Dojo? There is an entire library of ready to use widgets you can start building off for your applications today. We saw this when we created a datepicker, and there’s even widgets like select and button. Check out the widget showcase for more!

If you just wanted to quickly get started with a nice looking theme, you can use the Dojo theme. I’ll cover how to create your own themes another time, but for now, let’s assume you have a Dojo widget in your application and you really like the Dojo theme, but you would like to slightly make some adjustments. Not enough to warrant a custom theme, but maybe add some italic font, change a background here and there.

Dojo provides a way to do this in a CSS modules friendly manner. Every widget has a section in its documentation on the CSS classes you can override, such as those in the select widget. To get started, let’s add the Dojo theme to this widget.

import theme from "@dojo/themes/dojo";
...

// in your render method
<Select
  theme={theme} // apply the Dojo theme
  options={names}
  value={this.selectedValue}
  placeholder="Pick a name"
  onChange={this.onSelectChange}
/>

At this point, you’re able use a nice looking theme provided by Dojo. However, maybe I would like the placeholder text to be italicized, and I’d like to change the background color of the button and some other colors a bit. I don’t want to change much, just a few things.

.arrow {
  background: #959595;
  color: #fff;
}

.focused {
  color: #005e95;
}

.placeholder {
  font-style: italic;
}

.inputWrapper {
  color: #6e6e6e;
}

Now I have some simple CSS that matches the class names in the documentation. You can now apply these class names to your Select widget using a plain old JavaScript object that will map the extra classes to the widget key. You can see more detail here.

const SelectClasses = {
  "@dojo/widgets/select": {
    arrow: [css.arrow],
    focused: [css.focused],
    placeholder: [css.placeholder],
    inputWrapper: [css.inputWrapper]
  }
};

// in your render method
<Select
  theme={theme} // apply the Dojo theme
  classes={SelectClasses}
  options={names}
  value={this.selectedValue}
  placeholder="Pick a name"
  onChange={this.onSelectChange}
/>

This will now maintain the Dojo theme you are already using, but also apply the classes you are adding the widget as well. You end up with an application that looks something like below.

As you can see, it doesn’t take a lot of work to use the provided Dojo theme with your applications and apply your own little CSS sugar to the out-of-the-box Dojo widgets. You can also take this a step further and create your own custom theme for your application and even a reusable theme for a suite of applications! Happy dev’ing!

Build Time Rendering in Dojo

Dojo Build Time Rendering

You may have worked with other frameworks that support server side rendering. What it basically does is render the HTML of your page and pass it down to the client as it would look when the initial JavaScript loads and then you can interact with it and the JavaScript stuff works its magic to make a cool interactive application.

The Dojo method of doing this is much simpler. Instead of rendering the pages on the server, you can create your pages during the build process, and then you can just upload it anywhere.

You can find some information about Build Time Rendering on the github page for dojo/cli-build-app. You can get started by using the dojo/cli to quickly scaffold an application and modify it a bit.

Once you have your template application ready to go, let’s make some modifications. First thing we need to do in src/index.html is add a root div that the build time rendering tools can work with.

<!DOCTYPE html>
<html lang="en-us">
<head>
  <title>dojo-btr</title>
  <meta name="theme-color" content="#222127">
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <div id="root"></div> <!-- Add this element -->
</body>
</html>

Now we can set up the configuration for build time rendering. One thing to note is that by default, the template application uses hash routing, meaning that routes look like myapp/#about. This will generate a single index.html file in your build that will quickly load those routes. If you use a different history manager, it will create an index.html for each route.

{
  "build-app": {
    "build-time-render": {
      "root": "root",
      "paths": [
        "#home",
        "#about",
        "#profile"
      ]
  }
}

Note that I have prefixed my paths with a # so that the BTR can generate the pages correctly. The output of this is pretty interesting. Each route is stored in an array as strings, and as you change your route at runtime, it will load the HTML of that route as needed.

The benefit here is that your HTML is ready to go and the JavaScript parts just do their thing without having to do an initial render of your page. It makes for a very responsive experience. You get a lot of benefit from Build Time Rendering with some simple configuration, so take advantage of it!

You can see a sample of how this looks in a sample application I put together here. I also have it running live here.

Building a simple app in Dojo

dojo app

I’ve been thinking about how I could demonstrate building a basic Dojo application beyond a hello world app or a Todo app. There are some really good samples in the dojo/examples repo. Then I came across this react application for searching for emojis and who doesn’t have to search for emojis regularly, so I knew I found my demo. It also helps that the dojo template on Code Sandbox now uses TSX/JSX as the default.

Because the dojo template app uses JSX by default, it made this sample almost a complete one to one of the react sample. I won’t go into detail of this application line by line, but I do want to cover some core concepts it shows.

Get Meta

Meta in Dojo is meta information about your widget. Pretty meta right?

When you build Dojo widgets, you never touch the output HTML of your application. There is no widget method to get a reference to the DOM. This prevents you from inadvertently changing a DOM element that is referenced by Dojos virtual DOM engine, which would be bad. So don’t get too crazy here. But there are valid reasons for wanting to access a DOM node in your application. In the case of my emoji application, I am using a small library called clipboardjs to let me copy emojis to my clipboard from my application. This library requires I pass a DOM node it will use to copy data to the clipboard.

You can get this information in Dojo is via a meta. Dojo provides some metas out of the box for you, like Dimensions, Animations, Intersection, and more. You can implement your own custom meta to access DOM nodes using `@dojo/framework/widget-core/meta/Base`.

// src/widgets/ElementMeta.ts
import { Base as MetaBase } from "@dojo/framework/widget-core/meta/Base";

class ElementMeta extends MetaBase {
  get(key: string): Element {
    const node = this.getNode(key);
    return node as Element;
  }
}

export default ElementMeta;

The meta implements a get() method that will get the DOM node via a given key and return that DOM node. Now in my application, where I use clipboardjs, I can use my meta in combination with the this.meta() method of the Widget to get a referenced DOM node.

// src/widgets/EmojiResultsRow.tsx
import { tsx } from "@dojo/framework/widget-core/tsx";
import { WidgetBase } from "@dojo/framework/widget-core/WidgetBase";

import * as css from "./styles/EmojiResultsRow.m.css";

import ElementMeta from "./ElementMeta";
import * as Clipboard from "clipboard";

export interface EmojiResultsRowProperties {
  title: string;
  symbol: string;
}

export class EmojiResultsRow extends WidgetBase<EmojiResultsRowProperties> {
  clipboard: Clipboard = null;

  onAttach() {
    // use my meta to get a DOM node
    const element = this.meta(ElementMeta).get(this.properties.title);
    this.clipboard = new Clipboard(element);
  }
  onDetach() {
    this.clipboard.destroy();
  }

  protected render() {
    const { title, symbol } = this.properties;
    const codePointHex = symbol.codePointAt(0).toString(16);
    const src = `//cdn.jsdelivr.net/emojione/assets/png/${codePointHex}.png`;
    // provide a `key` property to my widget element to
    // reference with my meta
    return (
      <div
        key={title}
        classes={[css.root, "copy-to-clipboard"]}
        data-clipboard-text={symbol}
      >
        <img alt={title} src={src} />
        <span classes={[css.title]}>{title}</span>
        <span classes={[css.info]}>Click to copy emoji</span>
      </div>
    );
  }
}

export default EmojiResultsRow;

Now I am able to use my custom meta to get a DOM node created by my widget. This makes access to output DOM nodes flexible, but also protects me from shooting myself in the foot unintentionally. If I break my DOM, it is totally my fault now.

Core Widgets

Dojo provides a suite of widgets you can use for your own applications. This includes items like TimePicker, Select and layout widgets. For my application, I’m interested in having an input that I can use for search. Every time I update the input element, I want to filter the list of emojis shown in my application. So I’m going to wrap a TextInput widget so I can manage some local state and pass the value of the input to a filter method.

// src/widgets/SearchInput.tsx
...
export class SearchInput extends WidgetBase<SearchInputProperties> {
  @watch() private searchValue = "";

  private onChange(value) {
    if (!value) {
      return;
    }
    this.searchValue = value;
    const { handleChange } = this.properties;
    handleChange(value);
  }

  protected render() {
    return (
      <div classes={[css.root]}>
        <div>
          <TextInput
            placeholder="Search for emoji"
            value={this.searchValue}
            onInput={this.onChange}
          />
        </div>
      </div>
    );
  }
}

Yes, I could have used a regular <input type="text" /> here, but the TextInput is very convenient as it already has an onInput method I can use that passes the value of the input directly, and not an event I would need to do event.target.value which, because I am lazy, I can really appreciate. Then I would need to use a keyup event, and maybe do some handling for different keys to on whether I want to get my value and why hassle with all that when Dojo provides a nice way to do it already.

I am also taking advantage of the @watch decorator to manage local state in my widget. I talked about this method in more detail here. This makes it very simple to manage the value of my input at all times.

You can see the full application in action here.

You can see that building applications in Dojo provides some safety and flexibility for you to piece together everything you need to build solid, and awesome applications. Dojo isn’t just a toolkit anymore, it’s a full blown framework and has a lot to offer!

Intro to the Dojo Router

We took a quick look at the Dojo router when we reviewed the template application from the dojo cli. The template application provides almost everything you need to know about the Dojo router. But let’s take a little deeper look at routing.

Defining Routes

The template application does a great job of providing a clear way to configure your routes.

export default [
  {
    path: "home",
    outlet: "home",
    defaultRoute: true
  },
  {
    path: "about",
    outlet: "about"
  },
  {
    path: "profile/{username}",
    outlet: "profile"
  }
];

The routes are defined as an array of objects. each Route object has a RouteConfig interface with properties you can define. In the snippet above I have made one change. I have set the path for the profile route as profile/{username}. This means I will need to define a parameter to that route, which we’ll get to in a moment, but first let’s look at the options for a route config.

// dojo/framework/src/routing/interfaces.d.ts
export interface RouteConfig {
  path: string;
  outlet: string;
  children?: RouteConfig[];
  defaultParams?: Params;
  defaultRoute?: boolean;
}

That’s the beauty of working with TypeScript and Dojo, you can look at the types and interfaces of the code and use them as a guide as to how you should use the tools. The only required properties are path and outlet. One of the other properties we see defined in our configuration is the defaultRoute, which as you may have guessed is the default route of your application. Who says naming things is hard?!

The children property would be used if you had nested routes. You could also define some default parameters, which is really useful if you have a route the depends on parameters, and your route needs them to behave correctly.

Outlet

The first part of routing we need to look at is the Outlet. The Outlet is a higher order component that you use to wrap up widgets that are part of a designated route.

// src/App.ts
...
export default class App extends WidgetBase {
  protected render() {
    return v("div", { classes: [css.root] }, [
      w(Menu, {}),
      v("div", [
        // Outlet is where routes go
        // the decide which widgets
        // match each route
        w(Outlet, {
          key: "home",
          id: "home",
          renderer: () => w(Home, {})
        }),
        w(Outlet, {
          key: "about",
          id: "about",
          renderer: () => w(About, {})
        }),
        w(Outlet, {
          key: "profile",
          id: "profile",
          renderer: () => w(Profile, { username: "Dojo User" })
        })
      ])
    ]);
  }
}

Looking at the outlet, you can see that we define the id of the Outlet to match the Route configuration we defined. The actual widget rendered in the Route doesn’t have to match the id, but as you can see, it’s pretty good practice to do so. Keep the code readable please.

Outlets are pretty straightforward. Since they render the widget for a Route, they can also handle passing any URL parameters as properties to the widget.

Link and Parameters

Before we dive in to URL parameters, first we need to talk about how you can create a link to a route that is expecting parameters. We can define those parameters with a specific component in Dojo for working with routes, the Link component.

// src/widgets/Menu.ts
w(
  Link,
  {
    to: 'profile',
    key: 'profile',
    classes: [css.link],
    activeClasses: [css.selected],
    params: {
      username: 'odoe'
    }
  },
  ['Profile']
)

The Link component is designed specifically for creating links to routes and static paths in your application. They provide some sugar to regular anchor tags you can take advantage of in your apps. In this case, I am providing a value to the username parameter we defined for our route. This means that is will pass the object { username: ‘odoe’ } to my Outlet that I can then use to pass to my child widget.

// src/App.ts
w(Outlet, {
  key: 'profile',
  id: 'profile',
  renderer: ({ params }: MatchDetails) => {
    return w(Profile, { username: params.username });
  }
})

When you pass parameters to a URL in the Dojo router, your render method is passed the parameters for you to use in your application as needed. Now, although this method works fine, you can be more explicit in how you use your route parameters.

You can define query parameters in your routes and use them for more advanced usage. Let’s update the route configuration.

// src/routes.ts
export default [
  ...
  {
    path: "profile/{param}?{value}",
    outlet: "profile"
  }
];

Maybe we have different ways of searching for users in our backend API. We can search by name or id,.

// src/widgets/Menu.ts
w(
  Link,
  {
    to: 'profile',
    key: 'profile',
    classes: [css.link],
    activeClasses: [css.selected],
    params: {
      param: 'name',
      value: 'odoe'
    }
  },
  ['Profile']
)

Now we can update our Outlet to pass the correct information to the child widget.

// src/App.ts
w(Outlet, {
  key: 'profile',
  id: 'profile',
  renderer: ({ params, queryParams }: MatchDetails) => {
    const user = users.find((user: User) => {
      return user[params.param] == queryParams.value;
    }) as User;
    return w(Profile, { username: `${user.name} ${user.lastName}` });
  }
})

Now we have built a fairly generic way of passing parameters and values to our Outlet and being able to search for the correct username to use in our widget. We can search by the name value or an id value.

Default Parameters

So far we have been defining parameters in our Link, but maybe we want to define some default parameters directly in our route instead.

// src/routes.ts
export default [
  ...
  {
    path: 'profile/{param}?{value}',
    outlet: 'profile',
    defaultParams: {
      param: 'id',
      value: '2'
    }
  }
];

For out default route, we can decide to search by id with a value of 2. When you start dealing with URL parameters, everything is a string, so if you wanted to use real numbers, you would need to do some additional sanitization in your application, but I think we’ve dived pretty deep into setting up the Dojo router for starter use. Big thanks to Anthony Gubler for helping me out with some of my router questions, it was a big help.

Summary

As you can see, the Dojo router is very flexible in how you want to define your routes and parameters. Depending on how your backend APIs are defined, you could create some very powerful and scalable applications!

Intro to the Dojo Registry

dojo registry

The Dojo Registry is a powerful way you can work with widgets in your applications. We’ve had the opportunity to touch on the Registry in previous posts. We used it to inject context into our application with Dojo Containers and it’s also used when you want to use routing in your applications.

Basic Registry

The Registry allows you to do a few different things in your application. You can register widgets with the registry as string values and then reference the string values anywhere throughout your app.

For example, maybe I want to define a string value for an AboutMe widget used in my About page.

// src/main.ts
import Registry from '@dojo/framework/widget-core/Registry';
...
// define widgets
import AboutMe from './widgets/AboutMe';

const registry = new Registry();
registry.define('about-me', AboutMe);

// src/widgets/About.ts
import WidgetBase from '@dojo/framework/widget-core/WidgetBase';
import { w, v } from '@dojo/framework/widget-core/d';

import * as css from './styles/About.m.css';

export default class About extends WidgetBase {
  protected render() {
    return v('h1', { classes: [css.root] }, [
      'About Page',
      // can reference the string value
      // of widget without importing it
      w('about-me', {}, [])
    ]);
  }
}

This is pretty useful, as it can keep some of your widget code clean and you can define a series of widgets in a single location as you register them. Things start to get really interesting when we use the local registry of the widget to handle lazy loading via a registry decorator.

Registry Decorator

Each widget has it’s own local registry that you can use to lazy load a widget until you need it. Maybe I want to have a button on my page that when clicked will load some other widget in its place. For the cli template application, maybe I have some profile information I want to show.

// src/widgets/MyProfile.ts
import WidgetBase from '@dojo/framework/widget-core/WidgetBase';
import { v } from '@dojo/framework/widget-core/d';

export default class MyProfile extends WidgetBase {
  protected render() {
    return v('aside', {}, [
      v('p', {}, [
        `I don't believe in the moon,
         I think it's just the back of the sun.`
      ])
    ]);
  }
}

This is a simple little widget, nothing too fancy. Now let’s add it to our profile widget with the registry decorator.

// src/widgets/Profile.ts
import { registry } from '@dojo/framework/widget-core/decorators/registry';
import { watch } from '@dojo/framework/widget-core/decorators/watch';

...

// the decorator allows us to use a dynamic import
// to lazy load this widget until it's needed
@registry('my-profile', () => import('./MyProfile'))
export default class Profile extends WidgetBase<ProfileProperties> {
  @watch() private _showProfile = false;

  private onButtonClick() {
    this._showProfile = true;
  }

  protected render() {
    const { username } = this.properties;
    // check if the `_showProfile` property
    // is true to determine if I should load
    // the `MyProfile` widget
    const node = this._showProfile ?
                  w('my-profile', {}) :
                  v('button', {
                    onclick: this.onButtonClick
                    }, [ 'Show Profile' ]);
    return v('h1', { classes: [css.root] }, [
      `Welcome ${username}!`,
      v('p', {} , [
        node
      ])
    ]);
  }
}

We can take advantage of the watch decorator to update a value of our widget to determine what our widget should look like. You can read more about the watch decorator in my previous blog post. That’s pretty awesome right! Because the registry decorator lets us use a dynamic import, the Dojo build system will create a bundle for that widget that can be lazy loaded. You can even do multiple entries with the registry decorator to allow access to multiple widgets in your own widgets.

You can see a demo of how this works in the following code sandbox.

Summary

The Registry is a pretty powerful tool you have at your disposal in Dojo. We’ve seen it used for routing, predefining a series of widgets you can use throughout your application, lazy-loading, and with containers and injectors. Once you get your feet wet with it, you’ll be swimming Registry goodness to help you build awesome applications!

Dojo CLI Template App

dojo cli template application

The release of Dojo 4 introduced some really nice new features in their build pipeline to optimize for progressive web apps, some performance improvements under the hood in their rendering engine, and more.

However, one of the really cool things I haven’t seen talked about too much is the new template application you get with the dojo cli. You can check out my earlier post on using the @dojo/cli to learn how to get started.

The previous template application gave a you a nice introduction to basic widgets and how to display the widget in your app. It was fine as an introduction, but if you wanted to do a little more, like routing, you had to do a little more research. Not anymore! The new template application comes with routing out of the box so you can quickly get up and running with a feature that you will probably end up using at some point in a larger application.

Unfortunately, the latest template app is not on code sandbox, most likely due to the routing not working correctly in that environment, at least not the last time I tried.

Here is what the template application looks like.

I have put up the untouched source for the template application on github.

Now let’s take a look at what you get with the new template app.

I’ll do a more detailed post on routing in the future, but you can read more details in the Dojo documentation. The key here is that each view for a route is defined by an Outlet. An Outlet is just a wrapper for widgets that will be displayed in that routes view.

// src/App.ts
import WidgetBase from "@dojo/framework/widget-core/WidgetBase";
import { v, w } from "@dojo/framework/widget-core/d";
import Outlet from "@dojo/framework/routing/Outlet";

import Menu from "./widgets/Menu";
import Home from "./widgets/Home";
import About from "./widgets/About";
import Profile from "./widgets/Profile";

import * as css from "./App.m.css";

export default class App extends WidgetBase {
  protected render() {
    return v("div", { classes: [css.root] }, [
      w(Menu, {}),
      v("div", [
        w(Outlet, {
          key: "home", id: "home", renderer: () => w(Home, {})
        }),
        w(Outlet, {
          key: "about", id: "about", renderer: () => w(About, {})
        }),
        w(Outlet, {
          key: "profile",
          id: "profile",
          renderer: () => w(Profile, {
            username: "Dojo User"
          })
        })
      ])
    ]);
  }
}

Ok, so let’s break this down a little bit. The w is a function to render widgets and v will create virtual dom nodes. You can see that in this case, what is happening is there is a top level menu, with a div underneath. In this div is where each Outlet is defined, with an id, key (optional), and what to display in the render method.

I won’t go in to detail on each view. They are fairly standard widgets, but let’s take a look at the routing part. The routes are defined in a simple object.

// src/routes.ts
export default [
  {
    path: "home",
    outlet: "home",
    defaultRoute: true
  },
  {
    path: "about",
    outlet: "about"
  },
  {
    path: "profile",
    outlet: "profile"
  }
];

Each route has a path, with the name of the outlet id, which coincides with the id of the outlet defined in the previous snippet. Super simple and straight forward. You can also see that the home route is defined as the defaultRoute.

Here is how the whole thing is put together.

// src/main.ts
import renderer from '@dojo/framework/widget-core/vdom';
import Registry from '@dojo/framework/widget-core/Registry';
import { w } from '@dojo/framework/widget-core/d';
import { registerRouterInjector } from '@dojo/framework/routing/RouterInjector';
import {
  registerThemeInjector
} from '@dojo/framework/widget-core/mixins/Themed';
import dojo from '@dojo/themes/dojo';
import '@dojo/themes/dojo/index.css';

import routes from './routes';
import App from './App';

const registry = new Registry();
registerRouterInjector(routes, registry);
registerThemeInjector(dojo, registry);

const r = renderer(() => w(App, {}));
r.mount({ registry });

I’ll go into more detail in the future, but you register your route with the Registry, which is a way that you can do more configuration with your widgets beyond just display them on the page. You even get a taste of working with themes via the ThemeInjector.

Once your routes are registered, you can then mount the application with the registry. If I were to do anything different here, it would probably be to do all the Registry work in a separate module, but that is just a preference.

I am really glad to see the new dojo cli template app giving users a solid start with routing and an introduction to the registry, which in my opinion are key components of building scalable applications.

Now, why is routing important in progressive web apps? It allows you to lazy load parts of your application until you need them. For example, in the template application some users may never click on the profile page, so why should your application load the files for that page unnecessarily . You can see what I mean in this animated image.

Here, you can see that the files for the pages are not loaded until I click on them. This is code splitting, something Dojo 1 was fantastic at and that the new Dojo takes advantage of webpack under the hood in their build tools to handle as well.

Watch for property changes in Widgets

dojo watch decorator

We’ve seen how you can manage more complex state in your Dojo applications with Containers, but with the release of Dojo 4 we now have access to a new @watch decorator.

This very useful for managing the internal state of your widgets, because you no longer have to concern yourself with having to call a widgets invalidate() method if you don’t want to.

For example, let’s say that I want to have a simple clock widget in my application that is just going to display the current time. For demo purposes, I’ll display the time up to the second.

I can create a Clock widget that will do exactly that.

class Clock extends WidgetBase {
  // use watch decorator so that any updates
  // to this property will now call the
  // internal invalidate() method and
  // rerender my widget
  @watch() private _currentTime = new Date();

  // a widget lifecycle method that is called
  // when a widget is added to the DOM
  onAttach() {
    // update time every second
    setInterval(() => {
      this._currentTime = new Date();
    }, 1000);
  }

  protected render() {
    return v("h1", { classes: css.root }, [
      `Time: ${this._currentTime.toLocaleTimeString()}!`
    ]);
  }
}

As you can see, this greatly simplifies my widget so that I can just update my internal state without having to worry about invalidating my widget. This is powerful stuff!

Here is a live demo of this application.

There have been some other great updates to Dojo 4 such as a simplified render method to mount Dojo widgets and much more!