dojo dragon main logo

Resource Templates

A resource template describes how Dojo resources interact with it's data-source based on the options passed. Resource templates are statically defined and used through an application to power "resource aware" widgets. There are two types of ResourceTemplate that can be used, a standard template and a template that accepts initialization options.

A ResourceTemplate consists of two APIs:

  • read()
    • The function responsible for fetching the resource data based on the ResourceReadRequest and setting it in the store.
  • find()
    • The function responsible for finding an item in the resource data based on the ResourceFindRequest and setting it in the store.

A ResourceTemplateWithInit adds an additional init API

  • init()
    • An initializer function designed to deal with data passed to widgets with the template.
interface ResourceTemplate<S = {}, T = {}> {
    read: ResourceRead<S>;
    find: ResourceFind<S>;
}

interface ResourceTemplateWithInit<S = {}, T = {}> {
    read: ResourceRead<S>;
    find: ResourceFind<S>;
    init: ResourceInit<S>;
}

Resource Controls

ResourceControls are injected as the second argument to all the ResourceTemplate APIs and need to be used to get existing cached data from the resource store and put items into the store.

export interface ResourceGet<S> {
    (request?: ResourceReadRequest<S>): ResourceReadResponse<S>;
}
export interface ResourcePut<S> {
    (readResponse: ResourceReadResponse<S>, readRequest: ResourceReadRequest<S>): void;
    (findResponse: ResourceFindResponse<S> | undefined, findRequest: ResourceFindRequest<S>): void;
}
export interface ResourceControls<S> {
    get: ResourceGet<S>;
    put: ResourcePut<S>;
}

read()

The ResourceTemplate.read function is responsible for fetching requested data for the resource and setting it in the store using the put resource control. There are no restrictions to how the data is sourced as long as the ResourceReadResponse is set in the store using the put resource control.

interface ResourceRead<S> {
    (request: ResourceReadRequest<S>, controls: ResourceControls<S>): void | Promise<void>;
}

The ResourceReadRequest has the offset, page size and query of the request. The query is a an object with the key mapping to a key of the resource item data interface for the associated value.

type ResourceQuery<S> = { [P in keyof S]?: any };

interface ResourceReadRequest<S> {
    offset: number;
    size: number;
    query: ResourceQuery<S>;
}

find()

The ResourceTemplate.find function is responsible for finding specific items within a resource based on the find criteria and setting it in the store using the put resource control. There are no restrictions to how the data is found as long as the ResourceFindResponse is set in the store using the put resource control.

export interface ResourceFind<S> {
    (options: ResourceFindRequest<S>, controls: ResourceControls<S>): void | Promise<void>;
}

The ResourceFindRequest has the current resource options, query, type and start index for the find request. The query is the same as the query object used with ResourceFindRequest, an object with the key mapping to a key of the resource item data interface for the associated value.

type FindType = 'exact' | 'contains' | 'start';

interface ResourceFindRequest<S> {
    options: ResourceOptions<S>;
    query: ResourceQuery<S>;
    start: number;
    type: FindType;
}
import { createResourceTemplate } from '@dojo/framework/core/middleware/resources';

interface User {
    firsName: string;
    lastName: string;
    username: string;
    email: string;
}

The type describes how to use the query to find the item in the resource, there are three different types.

  • contains (default)
    • Requesting an item where the value contains the the query item value
  • exact
    • Requesting an item that are an exact match of the query value
  • start
    • Requesting an item that where the value starts with the query value

init()

The init function is used to deal with options passed with the template using the resource middleware. These options are defined when creating the template using createResourceTemplateWithInit as the second generic parameter.

import { createResourceTemplateWithInit } from '@dojo/framework/core/middleware/resources';


// only showing the init api
const template = createResourceTemplateWithInit<{ foo: string }, { data: { foo: string; }[]; extra: number; }>({
    init: (options, controls) {
        // the options matches the type passed as the second generic
        const { data, extra } = options;
        // use the controls to work with the store, for example store the init data
        controls.put({ data, total: data.length});
    }
});

interface ResourceInitRequest<S> {
    id: string;
    data: S[];
}

export interface ResourceInit<S, I> {
    (request: I & { id: string; }, controls: ResourceControls<S>): void;
}

The init options are injected into the function along with the standard ResourceControls to be used to add the initialize the resource store.

Memory Resource Templates

Dojo resources offers a pre-configured memory resource template that implements the complete resource template API. The memory template is designed to work with data passed to a widget when using the template that initializes the resource store for the template. The memory template is created using the createMemoryResourceTemplate factory from @dojo/framework/core/middleware/resources, with the type of the resource data being passed to the factory.

MyWidget.tsx

import { create, tsx } from '@dojo/framework/core/vdom';
import { createMemoryResourceTemplate, createResourceMiddleware } from '@dojo/framework/core/middleware/resources';

interface ResourceItem {
    value: string;
}

interface MyWidgetProperties {
    items: ResourceItem[];
}

const resource = createResourceMiddleware();
const factory = create({ resource }).properties<MyWidgetProperties>();

const template = createMemoryResourceTemplate<ResourceItem>();

export default factory(function MyWidget({ id, properties, middleware: { resource } }) {
    const { items } = properties();
    return <MyResourceAwareWidget resource={resource({ template, initOptions: { id, data: items } } )}>
});

For more information please see the Using Resource Templates.

Custom Resource Templates

To connect a resource to an custom data-source, such as a RESTful API the createResourceTemplate() factory can be used. At a minimum the read API needs to be fulfilled with the additional init and find optional.

myResourceTemplate.ts

import { createResourceTemplate } from '@dojo/framework/core/middleware/resources';

interface MyResource {
    id: string;
    name: string;
    email: string;
}

export default createResourceTemplate<MyResource>({
    read: async (request: ResourceReadRequest, controls: ResourceControls) => {
        const { offset, size, query } = request;
        // use the request details to fetch the required set of data
        const url = `https://my-data-source.com?size=${size}&offset=${offset}&query${JSON.stringify(query)}`;
        const response = await fetch(url);
        const json = await response.json();
        controls.put({ data: json.data, total: json.total }, request);
    },
    find: (request: ResourceFindRequest, controls: ResourceControls) => {
        const { query, options, start, type } = request;
        // use the start, query, type and options to find an item from the data-source
        const url = `https://my-data-source.com/?start=${start}&type=${type}&find${JSON.stringify(query)}`;
        const response = await fetch(url);
        const json = await response.json();
        controls.put({ item: json.item, index: json.index }, request);
    }
});

Create a Resource Template with initialization options

If the resource template needs to support custom initialization the createResourceTemplateWithInit can be used, which requires the template to have an init API that will be called when a backing resource is created. The initialize options required are typed using the second generic on the factory function.

import { createResourceTemplateWithInit } from '@dojo/framework/core/middleware/resources';

interface MyResource {
    id: string;
    name: string;
    email: string;
}

export default createResourceTemplateWithInit<MyResource, { data: MyResource[] }>({
    init: (request: { id: string } & { data: MyResource[] }, controls: ResourceControls) => {
        const { data } = request;
        // adds any data passed with the template to resource store
        controls.put(data);
    },
    read: async (request: ResourceReadRequest, controls: ResourceControls) => {
        const { offset, size, query } = request;
        // use the request details to fetch the required set of data
        const url = `https://my-data-source.com?size=${size}&offset=${offset}&query${JSON.stringify(query)}`;
        const response = await fetch(url);
        const json = await response.json();
        controls.put({ data: json.data, total: json.total }, request);
    },
    find: (request: ResourceFindRequest, controls: ResourceControls) => {
        const { query, options, start, type } = request;
        // use the start, query, type and options to find an item from the data-source
        const url = `https://my-data-source.com/?start=${start}&type=${type}&find${JSON.stringify(query)}`;
        const response = await fetch(url);
        const json = await response.json();
        controls.put({ item: json.item, index: json.index }, request);
    }
});

Typing Resource Templates

The resource template factories all accept a generic that is used to type the shape of the resource data. It is highly recommended to provide typings to the template so that when the template is passed to a widget the typings for data and transform can be inferred correctly. As referenced in the previous examples, typing a resource requires passing the resource data type interface on creation of the template.

userResourceTemplate.ts

import { createResourceTemplate } from '@dojo/framework/core/middleware/resources';

interface User {
    firsName: string;
    lastName: string;
    username: string;
    email: string;
}

export default createResourceTemplate<User>({
    // the template implementation
});