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.
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.
- The function responsible for fetching the resource data based on the
find()
- The function responsible for
finding
an item in the resource data based on theResourceFindRequest
and setting it in the store.
- The function responsible for
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
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 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 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 createResourceTemplateWithInit
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 { 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
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
});