Available middleware
Dojo provides a variety of optional middleware that widgets can include when needing to implement specific requirements.
icache
A middleware that uses the invalidator
middleware functionality to provide a cache that supports lazy value resolution and automatic widget invalidation once a value becomes available. By default the cache will invalidate when a value is set in the cache, however there is an optional third argument on the set APIs that can be used to skip the invalidation when required.
API:
import icache from '@dojo/framework/core/middleware/icache';
icache.getOrSet<T = any>(key: any, value: any, invalidate: boolean = true): T | undefined
- Retrieves the cached value for the given
key
, if one exists, otherwisevalue
is set. In both instances,undefined
is returned if the cached value has not yet been resolved.
- Retrieves the cached value for the given
icache.get<T = any>(key: any): T | undefined
- Retrieves the cached value for the given
key
, orundefined
if either no value has been set, or if the value is still pending resolution.
- Retrieves the cached value for the given
icache.set(key: any, value: any, invalidate: boolean = true): any
- Sets the provided
value
for the givenkey
. Ifvalue
is a function, it will be invoked in order to obtain the actual value to cache. If the function returns a promise, a 'pending' value will be cached until the final value is fully resolved. In all scenarios, once a value is available and has been stored in the cache, the widget will be marked as invalid so it can be re-rendered with the final value available.
- Sets the provided
icache.has(key: any): boolean
- Returns
true
orfalse
based in whether the key is set in the cache.
- Returns
icache.delete(key: any, invalidate: boolean = true): void
- Remove the item from the cache.
clear(invalidate: boolean = true)
- Clears all values currently stored in the widget's local cache.
The current cache value is passed when a function is passed to icache.set
, the example below demonstrates using the current value to for an incrementing number.
icache.set('key', (current) => {
if (current) {
return current + 1;
}
return 1;
});
icache
can be typed in two different ways. One approach uses generics to enable the return type to get specified at the call-site, and for getOrSet
, the return type can get inferred from the value type. If the value
for getOrSet
is a function then the type will get inferred from the functions return type.
import { create, tsx } from '@dojo/framework/core/vdom';
import icache from '@dojo/framework/core/middleware/icache';
const factory = create({ icache });
interface FetchResult {
foo: string;
}
const MyIcacheWidget = factory(function MyIcacheWidget({ middleware: { icache } }) {
// `results` will infer the type of the resolved promise, `FetchResult | undefined`
const results = icache.getOrSet('key', async () => {
const response = await fetch('url');
const body: FetchResult = await response.json();
return body;
});
return <div>{results}</div>;
});
However this approach doesn't provide any typing for the cache keys. The preferred way to type icache
is to create a pre-typed middleware using createICacheMiddleware
. This allows for passing an interface which will create an icache
middleware typed specifically for the passed interface and provides type safety for the cache keys.
import { create, tsx } from '@dojo/framework/core/vdom';
import { createICacheMiddleware } from '@dojo/framework/core/middleware/icache';
interface FetchResult {
foo: string;
}
interface MyIcacheWidgetState {
key: FetchResult;
}
const icache = createICacheMiddleware<MyIcacheWidgetState>();
const factory = create({ icache });
const MyIcacheWidget = factory(function MyIcacheWidget({ middleware: { icache } }) {
// `results` will be typed to `FetchResult | undefined` based on the `MyIcacheWidgetState`
const results = icache.getOrSet('key', async () => {
const response = await fetch('url');
const body: FetchResult = await response.json();
return body;
});
return <div>{results}</div>;
});
theme
Allows widgets to theme their CSS classes when rendering, and also provides applications the ability to set themes and determine what the currently set theme is, if any.
Described in detail in the Dojo Styling and Theming reference guide.
API:
import theme from '@dojo/framework/core/middleware/theme';
theme.classes<T extends ClassNames>(css: T): T
- Widgets can pass in one or more of their CSS class names and will receive back updated names for the currently set theme that can be used when returning widget virtual nodes.
theme.set(css: Theme)
- Allows applications to set a specific theme.
theme.get(): Theme | undefined
- Returns the currently set theme, or
undefined
if no theme has been set. Typically used within an application's root widget.
- Returns the currently set theme, or
i18n
Allows widgets to localize their message text when rendering, and also provides applications the ability to set a locale and determine what the currently set locale is, if any.
Described in detail in the Dojo Internationalization reference guide.
API:
import i18n from '@dojo/framework/core/middleware/i18n';
i18n.localize<T extends Messages>(bundle: Bundle<T>, useDefaults = false): LocalizedMessages<T>
- Returns a set of messages out of the specified
bundle
that are localized to the currently set locale.useDefaults
controls whether messages from the default language are returned when corresponding values are not available for the current locale. Defaults tofalse
in which case empty values are returned instead of messages in the default language.
- Returns a set of messages out of the specified
i18n.set(localeData?: LocaleData)
- Allows applications to set a specific locale.
i18n.get()
- Returns the currently-set locale, or
undefined
if no theme has been set. Typically used within an application's root widget.
- Returns the currently-set locale, or
dimensions
Provides various size and position information about a widget's underlying nodes.
API:
import dimensions from '@dojo/framework/core/middleware/dimensions';
dimensions.get(key: string | number): Readonly<DimensionResults>
- Returns dimension information for the widget's specified DOM element, identified by the node's
key
property. If the node does not exist for the current widget (either has not yet been rendered or an invalid key was specified), all returned values are0
.
- Returns dimension information for the widget's specified DOM element, identified by the node's
The returned DimensionResults
contains the following properties, mapped from the specified DOM element's sources:
Property | Source |
---|---|
client.left |
node.clientLeft |
client.top |
node.clientTop |
client.width |
node.clientWidth |
client.height |
node.clientHeight |
position.bottom |
node.getBoundingClientRect().bottom |
position.left |
node.getBoundingClientRect().left |
position.right |
node.getBoundingClientRect().right |
position.top |
node.getBoundingClientRect().top |
size.width |
node.getBoundingClientRect().width |
size.height |
node.getBoundingClientRect().height |
scroll.left |
node.scrollLeft |
scroll.top |
node.scrollTop |
scroll.height |
node.scrollHeight |
scroll.width |
node.scrollWidth |
offset.left |
node.offsetLeft |
offset.top |
node.offsetTop |
offset.width |
node.offsetWidth |
offset.height |
node.offsetHeight |
intersection
Provides information on whether a node is visible in a particular viewport using the Intersection Observer API.
As the Intersection Observer API is still an emerging web standard, the framework automatically ensures the underlying API is made available when running an application in a browser that does not yet support it. Note that as of the v6 release of Dojo Framework, the Intersection Observer API v2 is not yet supported.
API:
import intersection from '@dojo/framework/core/middleware/intersection';
intersection.get(key: string | number, options: IntersectionGetOptions = {}): IntersectionResult
- Returns intersection information for the widget's specified DOM element, identified by the node's
key
property. If the node does not exist for the current widget (either has not yet been rendered or an invalid key was specified), a result is returned indicating zero intersection.
- Returns intersection information for the widget's specified DOM element, identified by the node's
The options
argument allows for more control on how intersection gets calculated. The available fields are the same as those for the intersection observer API options.
IntersectionResult
properties:
Property | Type | Description |
---|---|---|
intersectionRatio |
number |
The ratio of the element's bounding box that is intersecting the root element's viewport, from 0.0 to 1.0 . By default the root element is considered the browser's viewport unless an element is specified via the options.root argument. |
isIntersecting |
boolean |
A value of true indicates that the target element intersects with the root element's viewport (representing a transition into a state of intersection). A value of false indicates a transition from intersecting to not-intersecting. |
resize
Allows a widget to respond to resize events of its DOM nodes using a ResizeObserver
, and provides updated information on the node's new size when a resize occurs. Using this middleware is an effective way of creating applications that are responsive across a variety of viewport sizes.
As Resize Observer is still an emerging web standard, the framework automatically ensures the underlying API is made available when running an application in a browser that does not yet support it.
API:
import resize from '@dojo/framework/core/middleware/resize';
resize.get(key: string | number): DOMRectReadOnly | null
- Returns size information for the widget's specified DOM element, identified by the node's
key
property. If the node does not exist for the current widget (either has not yet been rendered or an invalid key was specified),null
is returned. The returned object is a standardDOMRectReadOnly
structure.
- Returns size information for the widget's specified DOM element, identified by the node's
breakpoint
Allows widgets to determine a specific width breakpoint that is matched given the current width of one of their virtual nodes. This middleware is useful when creating widgets that can adapt to a variety of display widths, such as widgets that work at both mobile and desktop resolutions.
Composes the resize
middleware to obtain the element width and to automatically invalidate the widget when its width is adjusted.
Note: If no custom width breakpoints are given, Dojo will default to the following set:
SM
: 0MD
: 576LG
: 768XL
: 960
API:
import breakpoint from '@dojo/framework/core/middleware/breakpoint';
interface Breakpoints {
[index: string]: number;
}
breakpoint.get(key: string | number, breakpoints: Breakpoints = defaultBreakpoints)
- Returns the breakpoint that the widget's specified output node (identified by its
key
) matches, given the node's current width. Custom breakpoints can be provided through thebreakpoints
argument. The return value is an object containing abreakpoint
property, identifying the name of the breakpoint that was matched, and acontentRect
property which contains the same value as callingresize.get(key)
would return.
- Returns the breakpoint that the widget's specified output node (identified by its
When using the same set of breakpoints in many locations, it is easier to define the set once rather than needing to pass it to every breakpoint.get()
call. Applications can define their own custom breakpoint middleware with appropriate defaults via:
src/middleware/myCustomBreakpoint.ts
import { createBreakpointMiddleware } from '@dojo/framework/core/middleware/breakpoint';
const myCustomBreakpoint = createBreakpointMiddleware({ Narrow: 0, Wide: 500 });
export default myCustomBreakpoint;
inert
Enables setting the inert
property on a node by key
. This will ensure that the node in question does not respond to actions such as focus, mouse event etc. For scenarios such as a dialog that is attached to the document.body
, inert
can be inverted onto all the siblings of the key
s node.
API:
import inert from '@dojo/framework/core/middleware/inert';
inert.set(key: string | number, enable: boolean, invert: boolean = false): void;
- Sets inert to the requested value for the node. When
invert
is passed the value will be set on all node's siblings.
- Sets inert to the requested value for the node. When
src/widgets/Dialog.tsx
import { tsx, create } from '@dojo/framework/core/vdom';
import inert from '@dojo/framework/core/middleware/inert';
import icache from '@dojo/framework/core/middleware/icache';
import * as css from './App.m.css';
const dialogFactory = create({ inert, icache }).properties<{
open: boolean;
onRequestClose: () => void;
}>();
const Dialog = dialogFactory(function Dialog({ children, properties, middleware: { inert } }) {
const { open } = properties();
inert.set('dialog', open, true);
if (!open) {
return null;
}
return (
<body>
<div
key="dialog"
styles={{
background: 'red',
width: '400px',
height: '400px',
marginLeft: '-200px',
marginTop: '-200px',
position: 'absolute',
left: '50%',
top: '50%'
}}
>
<button
onclick={() => {
properties().onRequestClose();
}}
>
Close
</button>
{children()}
</div>
</body>
);
});
const factory = create({ icache });
export default factory(function App({ middleware: { icache } }) {
return (
<div classes={[css.root]}>
<input />
<button
onclick={() => {
icache.set('open', true);
}}
>
Open
</button>
<Dialog
open={icache.getOrSet('open', false)}
onRequestClose={() => {
icache.set('open', false);
}}
>
<div>
<input />
<input />
<button>button</button>
Content
</div>
</Dialog>
</div>
);
});
store
Provides widgets access to their externalized state when using the Dojo stores component.
Described in detail in the Dojo Stores reference guide.
API:
import store from '@dojo/framework/core/middleware/store';
store.get<U = any>(path: Path<S, U>): U
- Retrieves the value from the store at the specified
path
. The composing widget will also be invalidated and re-rendered when the associated value is changed.
- Retrieves the value from the store at the specified
store.path(path: any, ...segments: any): StatePaths<S>
- Returns a store path beginning at a specified root with a number of additional segments.
store.at<U = any>(path: Path<S, U[]>, index: number)
- Returns a store path that includes a numeric index when accessing stored array values.
store.executor<T extends Process<any, any>>(process: T): ReturnType<T>
- Executes the given
process
within the composing widget's store and returns the result.
- Executes the given
focus
Allows widgets to inspect and control focus amongst their resulting DOM output when combined with the VDOM focus primitives.
API:
import focus from '@dojo/framework/core/middleware/focus';
focus.shouldFocus(): boolean
- Returns
true
if focus should be specified within the current render cycle. Will only returntrue
once, after whichfalse
is returned from future calls untilfocus.focus()
is called again. This function is typically passed as thefocus
property to a specific VDOM node, allowing the widget to direct where focus should be applied.
- Returns
focus.focus()
- Can be called to indicate that the widget or one of its children requires focus in the next render cycle. This function is typically passed as the
onfocus
event handler to outputted VDOM nodes, allowing widgets to respond to user-driven focus change events.
- Can be called to indicate that the widget or one of its children requires focus in the next render cycle. This function is typically passed as the
focus.isFocused(key: string | number): boolean
- Returns
true
if the widget's VDOM node identified by the specifiedkey
currently has focus. Returnsfalse
if the relevant VDOM node does not have focus, or does not exist for the current widget.
- Returns
Focus delegation example
The following shows an example of delegating and controlling focus across a widget hierarchy and output VNodes:
src/widgets/FocusableWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import focus from '@dojo/framework/core/middleware/focus';
import icache from '@dojo/framework/core/middleware/icache';
/*
The input's `onfocus()` event handler is assigned to a method passed in
from a parent widget, via the child's create().properties<MyPropertiesInterface>
API, allowing user-driven focus changes to propagate back into the application.
*/
const childFactory = create({ focus }).properties<{ onfocus: () => void }>();
const FocusInputChild = childFactory(function FocusInputChild({ middleware: { focus }, properties }) {
const { onfocus } = properties();
return <input onfocus={onfocus} focus={focus.shouldFocus} />;
});
const factory = create({ focus, icache });
export default factory(function FocusableWidget({ middleware: { focus, icache } }) {
const keyWithFocus = icache.get('key-with-focus') || 0;
const childCount = 5;
function focusPreviousChild() {
let newKeyToFocus = (icache.get('key-with-focus') || 0) - 1;
if (newKeyToFocus < 0) {
newKeyToFocus = childCount - 1;
}
icache.set('key-with-focus', newKeyToFocus);
focus.focus();
}
function focusNextChild() {
let newKeyToFocus = (icache.get('key-with-focus') || 0) + 1;
if (newKeyToFocus >= childCount) {
newKeyToFocus = 0;
}
icache.set('key-with-focus', newKeyToFocus);
focus.focus();
}
function focusChild(key: number) {
icache.set('key-with-focus', key);
focus.focus();
}
return (
<div>
<button onclick={focusPreviousChild}>Previous</button>
<button onclick={focusNextChild}>Next</button>
<FocusInputChild
key="0"
onfocus={() => focusChild(0)}
focus={keyWithFocus == 0 ? focus.shouldFocus : undefined}
/>
<FocusInputChild
key="1"
onfocus={() => focusChild(1)}
focus={keyWithFocus == 1 ? focus.shouldFocus : undefined}
/>
<FocusInputChild
key="2"
onfocus={() => focusChild(2)}
focus={keyWithFocus == 2 ? focus.shouldFocus : undefined}
/>
<FocusInputChild
key="3"
onfocus={() => focusChild(3)}
focus={keyWithFocus == 3 ? focus.shouldFocus : undefined}
/>
<FocusInputChild
key="4"
onfocus={() => focusChild(4)}
focus={keyWithFocus == 4 ? focus.shouldFocus : undefined}
/>
</div>
);
});
validity
Allows retrieving information specifically about a node's validity state which is useful for using the browser's built-in methods for validating form inputs and providing locale-based error messages.
API:
import validity from '@dojo/framework/core/middleware/validity';
validity.get(key: string, value: string)
- Return the validity state for the DOM element, identified by the node'skey
property. Returns{ valid: undefined, message: '' }
if the specified DOM element does not exist for the current widget. Otherwise, it returns aValidityState
object.
The ValidityState
object contains the following properties:
Property | Type | Description |
---|---|---|
valid |
boolean |
The value of the node's validity.valid property, stating whether the value of the node meets all of the validation constraints. |
validationMessage |
string |
The value of the node's validationMessage property, a localized message describing the failing constraints of the node's value. |
injector
Allows retrieving injectors from the Dojo registry and assigning invalidation callback functions to then.
Note: Injectors and the registry are advanced concepts not typically required when writing Dojo applications. They are mainly used internally by the framework to implement more advanced user-facing functionality such as Dojo stores.
API:
import injector from '@dojo/framework/core/middleware/injector';
injector.subscribe(label: RegistryLabel, callback: Function = invalidator)
- Subscribes the given
callback
invalidation function against the specified registrylabel
injector (if one exists). If acallback
is not specified, theinvalidator
middleware is used by default so that the current widget will be marked as invalid and re-rendered when the injector makes its data available.
- Subscribes the given
injector.get<T>(label: RegistryLabel): T | null
- Retrieves the current injector associated with the given registry
label
, ornull
if no such injector exists.
- Retrieves the current injector associated with the given registry
block
Allows widgets to execute modules known as blocks within Node.js at build time. Typically used as part of build-time rendering.
Described in detail in the Building reference guide.
API:
import block from '@dojo/framework/core/middleware/block';
block<T extends (...args: any[]) => any>(module: T)
- Executes the specified block module and returns its result.