dojo dragon main logo

Resource Templates

A resource template describes how Dojo resources interact with its data-source based on the options passed. Resource templates are statically defined and used throughout 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.

The resource API required is determined by the resource aware widget, by default the only API required is a read function designed to put all resource data based on the request options. Dojo resources provides a default template that is designed to work with data already available in the application, this data is passed into the template factory when it is used. To create the default template, use the createResourceTemplate without passing a template API, only passing the key of the unique id property of the template.

interface ResourceData {
	id: string;
	name: string;
}

const template = createResourceTemplate<ResourceData>('id');

Templates can be created as a factory that is used to initialise a template with state such as a data set or other custom information. This created using the same factory,

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 contains 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 contains 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 and passing an interface for the required options, createResourceTemplate<RESOURCE, INIT> as the second generic parameter.

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


// only showing the init api
const template = createResourceTemplate<{ 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.

Default Resource Templates

Dojo resources offers a pre-configured default resource template that implements the complete resource template API. The default 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 createResourceTemplate factory from @dojo/framework/core/middleware/resources passing no arguments.

MyWidget.tsx

import { create, tsx } from '@dojo/framework/core/vdom';
import { createResourceTemplate, 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 = createResourceTemplate<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 while init and find are 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 createResourceTemplate can be used. This 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 { createResourceTemplate } from '@dojo/framework/core/middleware/resources';

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

export default createResourceTemplate<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

All resource template factories 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
});