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
});