Resource Middleware
The resource
middleware is required to use resource templates and access resources, the middleware optionally automatically decorates the widget with the required resource
property depending on whether an interface is passed to the createResourceMiddleware
factory. The resource
property is used by the widget to interact with any resource passed. The resource
middleware passed to the widget is a factory that returns back the complete API for working with resources. The simplest scenario is using the resource
middleware to return data for a requested page. This is done using the getOrRead
API that requires the template and the resource options, getOrRead(template, options())
, the getOrRead
function is designed to be reactive, meaning if the data is not immediately available (i.e. the resource is asynchronous and not already been read for the provided options) then it will return undefined
for each page requested, so that the widget can deal with the "loading" data scenario.
The resource
property consists of the template
and an optional set of options
, these are used to interact with the template'
s resource store. As the options
can be undefined, they needed to be defaulted with options created using the createOptions
API. The createOptions
function takes an identifer used to track the options across renders. This id
can usually use the widgets id
injected into the widget along with the properties, children and middleware.
MyResourceAwareWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import { createResourceMiddleware } from '@dojo/framework/core/middleware/resources';
interface ResourceData {
value: string;
label: string;
}
const resource = createResourceMiddleware<ResourceData>();
const factory = create({ resource });
export default factory(function MyDataAwareWidget({ id, properties, resource }) {
const { getOrRead, createOptions } = resource;
const {
resource: { template, options = createOptions(id) }
};
const [items] = getOrRead(template, options());
if (!items) {
return <div>Loading...</div>;
}
return <ul>{items.map((item) => <li>{item.label}</li>)}</ul>;
});
resource
middleware API
createOptions()
createOptions
creates a new instance of options that can be used with the resource
API, an id
is required in order to identify the options instance across renders. The result of the createOptions
function should be used when working with the getOrRead
, isLoading
, isFailed
and find
APIs. It is important to use options
rather than constructing a new ResourceOptions
object in order to ensure that resources that correctly invalidate when options have changed.
const options = createOptions('id');
The resulting options
variable is a function that can be used to set and get the instances option data
interface ResourceOptions<S> {
page: number | number[];
query: ResourceQuery<S>;
size: number;
}
getOrRead()
The getOrRead
function takes a template
, the ResourceOptions
and optionally any initOptions
that are required. getOrRead
returns an array of data for each of the pages requested in the passed ResourceOptions
. If the data is not already available, it will perform a read
using the passed template. Once the data
has been set in the resource, the widget will be invalidated in a reactive manner.
MyResourceAwareWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import { createResourceMiddleware } from '@dojo/framework/core/middleware/resources';
const resource = createResourceMiddleware<{ value: string }>();
const factory = create({ resource });
export default factory(function MyDataAwareWidget({ id, properties, middleware: { resource } }) {
const { getOrRead, createOptions } = resource;
const {
resource: { template, options = createOptions(id) }
} = properties();
const [pageTenItems] = getOrRead(options({ page: 10, size: 30 }));
if (!pageTenItems) {
return <div>Loading...</div>;
}
return <ul>{pageTenItems.map((item) => <li>{item.label}</li>)}</ul>;
});
The query
object can be passed to specify a filter for a property of the data. If a transform
was passed with the template
then this query object will be mapped back to the original resources key when passed to the resource template's read
function.
MyResourceAwareWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import { createResourceMiddleware } from '@dojo/framework/core/middleware/resources';
const resource = createResourceMiddleware<{ value: string }>();
const factory = create({ resource });
export default factory(function MyDataAwareWidget({ id, properties, middleware: { resource } }) {
const { getOrRead, createOptions } = resource;
const {
resource: { template, options = createOptions(id) }
} = properties();
const [pageTenItems] = getOrRead(template, options({ page: 10, size: 30, query: { value: 'query-value' } }));
if (!pageTenItems) {
return <div>Loading...</div>;
}
return <ul>{pageTenItems.map((item) => <li>{item.label}</li>)}</ul>;
});
Multiple pages can be passed in the options
, each of the pages requested will be returned in the resulting array. When requesting multiple pages it's not safe to check the first array value to check if the getOrRead
call could be fulfilled as it API will return any pages that were available and make the requests for the rest. To check the status of the request call the options can be passed into the isLoading
API. The pages are return in the same order that they are specified in the options
, it can be useful to use .flat()
on the array once it has been fully loaded.
MyResourceAwareWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import { createResourceMiddleware } from '@dojo/framework/core/middleware/resources';
const resource = createResourceMiddleware<{ value: string }>();
const factory = create({ resource });
export default factory(function MyDataAwareWidget({ id, properties, middleware: { resource } }) {
const { getOrRead, createOptions } = resource;
const {
resource: { template, options = createOptions(id) }
} = properties();
// [pageOne, pageTwo, pageThree, pageFour]
const items = getOrRead(options({ page: [1, 2, 3, 4], size: 30 }));
if (!isLoading(options())) {
return <div>Loading...</div>;
}
return <ul>{items.flat().map((item) => <li>{item.label}</li>)}</ul>;
});
meta()
The meta
API returns the current meta information for the resources, including the current options themselves. The MetaResponse
is also contains the registered total
of resource, which can be used to determine conditional logic such as virtual rendering.
meta(template, options, request): MetaResponse;
meta(template, options, initOptions, request): MetaResponse;
MyResourceAwareWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import { createResourceMiddleware } from '@dojo/framework/core/middleware/resources';
const resource = createResourceMiddleware<{ value: string }>();
const factory = create({ resource });
export default factory(function MyDataAwareWidget({ id, properties, middleware: { resource } }) {
const { meta, createOptions } = resource;
const {
resource: { template, options = createOptions(id) }
} = properties();
// get the meta info for the current options
const metaInfo = meta(template, options());
if (metaInfo && metaInfo.total > 0) {
// do something if there is a known total
}
});
By default calling meta will not initiate a request, meaning if there is not meta info (as getOrRead
has not been called) then it will never populate them and invalidate without a separate call to getOrRead
. A additional parameter, request
can be passed as true
in order to make a request for the passed options if there is no existing meta information.
MyResourceAwareWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import { createResourceMiddleware } from '@dojo/framework/core/middleware/resources';
const resource = createResourceMiddleware<{ value: string }>();
const factory = create({ resource });
export default factory(function MyDataAwareWidget({ id, properties, middleware: { resource } }) {
const { meta, createOptions } = resource;
const {
resource: { template, options = createOptions(id) }
} = properties();
// get the meta info for the current options and make a `getOrRead`
// request if there is no existing meta information. Once the request
// is completed the widget will re-render with the meta information
const metaInfo = meta(template, options(), true);
if (metaInfo && metaInfo.total > 0) {
// do something if there is a known total
}
});
find()
The find
function takes the template
, ResourceFindOptions
and initOptions
if required by the template. find
returns the ResourceFindResult
or undefined
depending on if the item could be found. The ResourceFindResult
contains the identified item, the index of the resource data-set, the page the item belongs to (based on the page size set in the options
property in the ResourceFindOptions
) and the index of the item on that page. If the find
result is not already known to the resource
store and the request is asynchronous then the find
call will return undefined
and invalidate the widget once the find result available.
The ResourceFindOptions
requires a starting index, start
, the ResourceOptions
, options
, the type of search, type
(contains
is the default find type) and a query object for the find operation:
interface ResourceFindOptions {
options: ResourceOptions;
start: number;
type: 'exact' | 'contains' | 'start';
query: ResourceQuery;
}
MyResourceAwareWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import { createResourceMiddleware } from '@dojo/framework/core/middleware/resources';
const resource = createResourceMiddleware<{ value: string }>();
const factory = create({ resource });
export default factory(function MyDataAwareWidget({ id, properties, middleware: { resource } }) {
const { find, createOptions } = resource;
const {
resource: { template, options = createOptions(id) }
} = properties();
const item = find(template, { options: options(), start: 0, type: 'contains', query: { value: 'find query' } });
if (item) {
return <div>{/* do something with the item */}</div>;
}
return <div>Loading</div>;
});
isLoading()
The isLoading
function takes a template
, ResourceOptions
or ResourceFindOptions
object and initOptions
if required by the template. isLoading
returns a boolean
to indicate if there is a in-flight read underway for the passed options.
MyResourceAwareWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import { createResourceMiddleware } from '@dojo/framework/core/middleware/resources';
const resource = createResourceMiddleware<{ value: string }>();
const factory = create({ resource });
export default factory(function MyDataAwareWidget({ id, properties, middleware: { resource } }) {
const { getOrRead, isLoading, createOptions } = resource;
const {
resource: { template, options = createOptions(id) }
} = properties();
const [items] = getOrRead(template, options({ page: 1, size: 30 }));
if (!isLoading(template, options())) {
return <div>Loading...</div>;
}
return <ul>{items().map((item) => <li>{item.label}</li>)}</ul>;
});
isFailed()
The isFailed
function takes a template
, ResourceOptions
or ResourceFindOptions
object and initOptions
if required by the template. isFailed
returns a boolean
to indicate if there is a in-flight read underway for the passed options.
MyResourceAwareWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import { createResourceMiddleware } from '@dojo/framework/core/middleware/resources';
const resource = createResourceMiddleware<{ value: string }>();
const factory = create({ resource });
export default factory(function MyDataAwareWidget({ id, properties, middleware: { resource } }) {
const { getOrRead, isLoading, createOptions } = resource;
const {
resource: { template, options = createOptions(id) }
} = properties();
const [items] = getOrRead(template, options({ page: 1, size: 30 }));
if (!isFailed(template, options())) {
return <div>Failed...!</div>;
}
return <ul>{items().map((item) => <li>{item.label}</li>)}</ul>;
});