Functions
-
getUniqueId : {string}
-
For the most part, VComponents should not need to render ids on child content. However, in some cases this may be necessary. For example, in order to set up a relationship between a label and the element that the label references, the label and labeled content must rendezvous on a common id. Specifying fixed ids is problematic as this can lead to conflicts with other ids on the page. The getUniqueId() method helps solve this problem by creating producing an id that is guaranteed not to conflict with ids rendered by other components.
The id returned by getUniqueId() is typically used to provide a prefix (or suffix) for what would otherwise be a static id for some element rendered by the VComponent.
The usage model is:
- In the VComponent's constructor, check to see whether the VComponent already has a props.id value. If so, this can be used as a prefix for other ids and calling getUniqueId() is unnecessary.
- Otherwise, call getUniqueId() to retrieve the unique prefix for this component
- Store the result of the #1/#2 in an instance variable for later use.
- When rendering, use the previously stored prefix to generate unique ids for any elements that need them.
- Don't forget to include "id" in the list of ObservedGlobalProps in order to ensure that the VComponent receives the value of this global HTML attribute.
Putting this all together, we end up with a component like this:
import { Component, ComponentChild } from 'preact'; import { customElement, ExtendGlobalProps, ObservedGlobalProps, getUniqueId } from 'ojs/ojvcomponent'; import "ojs/ojinputtext"; import "ojs/ojlabel"; export type Props = ObservedGlobalProps<'id'>; @customElement('oj-demo-unique-id') export class DemoUniqueId extends Component<ExtendGlobalProps<Props>> { private uniquePrefix: string; constructor(props: Readonly<Props>) { super(props) this.uniquePrefix = props.id ?? getUniqueId(); } render(): ComponentChild { const inputTextId = `${this.uniquePrefix}_input`; return ( <div> <oj-label for={ inputTextId }>Label</oj-label> <oj-input-text id={ inputTextId } value="Value"/> </div> ); } }
Returns:
- Type
- string
-
registerCustomElement<P, M extends Record<string, (...args) => any> = {}>(tagName, functionalComponent, options) : {VComponent}
-
Class-based VComponents use the @customElement decorator to specify the VComponent's custom element tag name (also known as its full name) and to register the custom element with the JET framework. However, function-based VComponents cannot utilize this approach because decorators are only supported for classes and their constituent fields.
JET provides an alternate mechanism for registering a function-based VComponent and specifying its custom element tag name. The registerCustomElement method accepts three arguments: the custom element tag name to be associated with the VComponent, a reference to the Preact functional component that supplies the VComponent implementation, and a reference to additional options that can be specified when registering the function-based VComponent (see Options for futher details). It returns a higher-order VComponent that is registered with the framework using the specified custom element tag name.
Here is a simple example:
import { registerCustomElement } from 'ojs/ojvcomponent'; export type Props = Readonly<{ message?: string; }>; export const DemoFunctionBasedVComp = registerCustomElement( 'oj-demo-based-vcomp', ({ message='This is a function-based VComponent!' }: Props) => { return <div>{message}</div>; } );
There are some other considerations to keep in mind when implementing function-based VComponents:
- Function-based VComponents can use an anonymous function to implement their Preact functional component, and expose the returned higher-order VComponent as their public API.
- The registration call ensures that the returned higher-order VComponent extends the Preact functional component's custom properties with the required global HTML attributes defined by GlobalProps.
- Default custom property values are specified using destructuring assignment syntax in the function implementation.
- TypeScript can typically infer the type parameters to the
registerCustomElement
call without having to explicitly specify them in your code. However, if the function-based VComponent exposes public methods then the secondM
type parameter (which maps public method names to their function signatures) must be provided. In addition:- the Preact functional component that implements VComponent must be wrapped inline with a forwardRef call, and
- the
useImperativeHandle
hook must be used within the Preact implementation.
Parameters:
Name Type Argument Description tagName
string The custom element tag name for the registered function-based VComponent. functionalComponent
function The Preact functional component that supplies the VComponent implementation. options
Options.<P, M> <optional>
Additional options for the function-based VComponent. Returns:
Higher-order VComponent that wraps the Preact functional component.
- Type
- VComponent
-
Root
-
Root is a Preact component that can be used to wrap the VComponent's child content. This component should only be used for cases where the VComponent needs to control how observed global properties are rendered on the component's root custom element. In all other cases the non-wrapped child content should be returned directly.
See the Root Element section for more details.
Decorators
-
consumedContexts(contexts)
-
Class decorator for VComponent custom elements that specifies a list of Preact Contexts whose values should be made available to the inner virtual dom tree of the VComponent when rendered as an intrinsic element. This allows the inner virtual dom tree to have access to the Context values from the parent component when rendered either directly as part of the parent component's virtual dom tree or when rendered as template slot content in a parent VComponent. Note that any intrinsic elements within the inner virtual dom tree must also specify a list of Contexts to further propagate their values.
Parameters:
Name Type Description contexts
Array.<Context.<any>> The list of Preact Contexts -
customElement(tagName)
-
Class decorator for VComponent custom elements. Takes the tag name of the custom element.
Parameters:
Name Type Description tagName
string The custom element tag name -
method
-
Method decorator for VComponent class methods that should be exposed on the custom element as part of its public API. Non-decorated VComponent class methods will not be made available on the custom element.
Type Definitions
-
Action<Detail extends object = {}>
-
The Action type is used to identify properties as action callbacks. Actions are functions that the VComponent invokes when it wants to communicate some activity to the outside world. When the VComponent is being consumed as a custom element, this results in an actual DOM CustomEvent being dispatched. Alternatively, when the VComponent is referenced via its Preact Component class, the provided callback is invoked directly and no CustomEvent is produced.
Actions have an optional detail type, specified by an optional generic type parameter to the Action type. If the type parameter is supplied when the the action callback property is defined, then a detail value of that specified type is either passed to the consumer via the CustomEvent detail payload for the custom element case, or is directly passed as an argument of the callback function for the Preact component case.
Note that Action properties must adhere to a specific naming convention: "on<PascalCaseEventName>". For the custom element case, the type of the CustomEvent will be derived by converting the first character of the event name to lower case. Thus, the "onGreetingComplete" property will generate a CustomEvent of type "greetingComplete".
See Actions and Events for more info.
Signature:
[keyof Detail] extends [never] ? (detail?: Detail) => void : (detail: Detail) => void
-
Bubbles
-
As discussed in Actions and Events, the custom events generated by Actions do not bubble by default. The Bubbles marker type can be combined with the Action type to indicate that the Action's custom events should bubble.
type Props = { // This action generates bubbling custom events onThisActionBubbles?: Action & Bubbles; // This action generates non-bubbling custom events onThisActionDoesNotBubble?: Action; }
-
CancelableAction<Detail extends object = {}>
-
Some JET Web Components support an asynchronous, event-based cancelation contract where prior to performing some operation, the component dispatches a "cancelable" event. If the application cancels the event, the operation is not performed. The <oj-file-picker>'s ojBeforeSelect event is one example of such an API.
The VComponent API has built-in support for this pattern via the CancelableAction type. Like the plain old Action type, CancelableAction is a function type that is used for defining callback-centric properties. One key difference between these types is that CancelableAction returns a Promise. If the Promise resolves successfully, the action is considered to be accepted. If the Promise is rejected, the action is canceled.
As with Action-typed properties, CancelableActions exhibit different behavior depending on whether the VComponent is being consumed as a custom element or via its Preact Component class.
When consumed as a custom element, invoking a CancelableAction results in a CustomEvent being created and dispatched. The detail payload of this custom event is augmented with one extra field: an "accept" method. The accept method takes a single argument: the Promise that produces the cancelation result.
When consumed via the Preact Component class, no custom event is dispatched. Instead, the callback function returns the cancelation promise directly.
Signature:
[keyof Detail] extends [never] ? (detail?: Detail) => Promise<void> : (detail: Detail) => Promise<void>
-
Contexts
-
The Contexts type allows a function-based VComponent to specify a list of Preact Contexts whose values should be made available to the inner virtual dom tree of the VComponent when rendered as an intrinsic element. This allows the inner virtual dom tree to have access to the Context values from the parent component when rendered either directly as part of the parent component's virtual dom tree or when rendered as template slot content in a parent VComponent. Note that any intrinsic elements within the inner virtual dom tree must also specify a list of Contexts to further propagate their values.
Signature:
{ consume?: Array<Context<any>> }
-
DynamicSlots
-
In most cases when a Web Component accepts slot content, the number and names of the slots are known, as these are defined by the component's public API. However, in some cases components may allow an arbitrary number of slots to be specified, where the slot names are not known up front. The <oj-switcher> component is an example of a component that accepts a dynamically defined (rather than predefined) set of slots.
The VComponent API supports such cases via the DynamicSlots and DynamicTemplateSlots types. A single property can be marked with the DynamicSlots type:
type Props = { // This property will be populated with all // "unknown" slots. items?: DynamicSlots; // Other properties go here... }
When the VComponent is consumed in custom element form, this property will be populated with entries for each "dynamic" slot. That is, an entry will be added for each child element with a slot attribute that does not correspond to a known Slot-typed property. The property acts as a map from slot name to Slot instance. The VComponent can use whatever heuristic it prefers to decide which (if any) slots should be included in the rendered output.
Signature:
Record<string, ojvcomponent.Slot>
-
DynamicTemplateSlots<Data>
-
The DynamicTemplateSlots type is a companion to DynamicSlots that is used in cases where the component accepts an arbitrary number of template slots. VComponents may declare a single property of type DynamicTemplateSlots. When the component is used as a custom element, this property will be populated with one entry for each "dynamic" template slot, where the key is the slot name and the value is a corresponding TemplateSlot function.
Different TemplateSlot functions may require different context objects as arguments, so DynamicTemplateSlots can accept a union type for its type parameter. The DynamicTemplateSlots type definition is structured to ensure that each union sub-type gets mapped to a separate TemplateSlot function.
Note that each VComponent can only contain a single dynamic slot property. That is, each VComponent can have one property of type DynamicSlots or one property of type DynamicTemplateSlots, but not both.
Signature:
Record<string, (Data extends object ? ojvcomponent.TemplateSlot<Data> : never)>
-
ElementReadOnly<T>
-
By default, writeback property mutations can be driven either by the component, typically in response to some user interaction, or by the consumer of the component. In some cases, writeback properties are exclusively mutated by the component itself. Writeback properties that cannot be mutated by the consumer are known as read-only writeback properties. The <oj-input-text>'s rawValue property is an example of such a property.
Read-only writeback properties are declared in a similar manner to plain old writeback properties, with one important difference: the ElementReadOnly utility type is used as a marker to identify the that the property is read-only.
Declarations for both forms of writeback properties can be seen below:
type Props = { // This is a normal writeback property: value?: string; onValueChanged?: PropertyChanged<string> // This is a read-only writeback property: rawValue?: ElementReadOnly<string>; onRawValueChanged?: PropertyChanged<string> }
Signature:
T
- Deprecated:
-
Since Description 12.0.0
Use the ReadOnlyPropertyChanged type instead. -
ExtendGlobalProps<Props>
-
As discussed in the Properties section, all VComponents must minimally include the GlobalProps in their property types. ExtendGlobalProps is a convenience type for combining component-specific properties with GlobalProps, e.g.:
import { customElement, ExtendGlobalProps } from 'ojs/ojvcomponent'; // These are the component-specific props: type Props = { greeting?: string; name?: string; } // Below we merge the component props with the // global props using ExtendGlobalProps @customElement('oj-hello-world-with-props') export class HelloWorld extends Component<ExtendGlobalProps<Props>> {
Signature:
Readonly<Props> & ojvcomponent.GlobalProps
-
GlobalProps
-
The GlobalProps type defines the set of global properties that are supported by all VComponents. This includes three categories of properties:
The following properties are included from category 1:
- accessKey
- autocapitalize
- autofocus
- class
- contentEditable
- dir
- draggable
- enterKeyHint
- hidden
- id
- inputMode
- lang
- role
- slot
- spellcheck
- style
- tabIndex
- title
- translate
The following ARIA-specific attributes are included from category 2:
- aria-activedescendant
- aria-atomic
- aria-autocomplete
- aria-busy
- aria-checked
- aria-colcount
- aria-colindex
- aria-colspan
- aria-controls
- aria-current
- aria-describedby
- aria-details
- aria-disabled
- aria-errormessage
- aria-expanded
- aria-flowto
- aria-haspopup
- aria-hidden
- aria-invalid
- aria-keyshortcuts
- aria-label
- aria-labelledby
- aria-level
- aria-live
- aria-modal
- aria-multiline
- aria-multiselectable
- aria-orientation
- aria-owns
- aria-placeholder
- aria-posinset
- aria-pressed
- aria-readonly
- aria-relevant
- aria-required
- aria-roledescription
- aria-rowcount
- aria-rowindex
- aria-rowspan
- aria-selected
- aria-setsize
- aria-sort
- aria-valuemax
- aria-valuemin
- aria-valuenow
- aria-valuetext
The following event listener properties are included from category 3:
- onBlur
- onClick
- onContextMenu
- onDblClick
- onDrag
- onDragEnd
- onDragEnter
- onDragExit
- onDragLeave
- onDragOver
- onDragStart
- onDrop
- onFocus
- onKeyDown
- onKeyPress
- onKeyUp
- onMouseDown
- onMouseEnter
- onMouseLeave
- onMouseMove
- onMouseOut
- onMouseOver
- onMouseUp
- onPointerOver
- onPointerEnter
- onPointerDown
- onPointerMove
- onPointerUp
- onPointerCancel
- onPointerOut
- onPointerLeave
- onTouchCancel
- onTouchEnd
- onTouchMove
- onTouchStart
- onWheel
- onfocusin
- onfocusout
The above event listener properties can also be specified with the "Capture" suffix (e.g., "onClickCapture") to indicate that the listener should be registered as a capture listener.
-
ImplicitBusyContext
-
As discussed in Children and Slots, JET application developers may require a mechanism for waiting at runtime until all of a slot's component contents have been created and initialized before programmatically interacting with the contents. VComponent developers can request that a BusyContext instance, scoped to a VComponent's children or to a particular named slot's contents, be created at runtime by using the ImplicitBusyContext marker type. This marker type can be combined with Preact's ComponentChildren type or with the Slot type as needed:
import { Component, ComponentChildren } from 'preact'; import { customElement, ExtendGlobalProps, ImplicitBusyContext, Slot } from 'ojs/ojvcomponent'; type Props = { // This indicates that the VComponent accepts arbitrary (non-slot) children, // and that a BusyContext scoped to this child content should be created at runtime children?: ComponentChildren & ImplicitBusyContext; // This indicates that the VComponent accepts a slot named "end", // and that a BusyContext scoped to the slot's content should be created at runtime end?: Slot & ImplicitBusyContext; }
-
Methods<M>
-
The Methods type specifies optional design-time method metadata that can be passed in the
options
argument when calling registerCustomElement to register a function-based VComponent that exposes custom element methods.The Methods type makes several adjustments to the MetadataTypes.ComponentMetadataMethods type:
-
The
internalName
property does not apply to VComponents. - The return type and parameter types are explicitly omitted from MetadataTypes.ComponentMetadataMethods and MetadataTypes.MethodParam respectively, as these should come from the function signatures passed as a type parameter to the registerCustomElement call.
-
Optional
apidocDescription
andapidocRtnDescription
properties are added to specify markup text for inclusion in the generated API Doc describing the method and its return value, respectively. IfapidocDescription
is unspecified, then thedescription
property is used in the API Doc.
-
The
Signature:
{Partial<Record<keyof M, Omit<MetadataTypes.ComponentMetadataMethods, 'internalName' | 'params' | 'return'> & { params?: Array<Omit<MetadataTypes.MethodParam, 'type'>>; apidocDescription?: string; apidocRtnDescription?: string; }>>}
- Deprecated:
-
Since Description 16.0.0
Use doclet metadata within the type alias that maps method names to function signatures instead. -
ObservedGlobalProps
-
The ObservedGlobalProps type is used to identify the subset of GlobalProps that the VComponent implementation needs to observe. When a VComponent is used as a custom element, ObservedGlobalProps determines which of the GlobalProps values will be extracted from the DOM and passed into the VComponent. Properties that are selected using ObservedGlobalProps are also included in the custom element's observedAttributes list. As a result, updates to observed global properties will trigger the VComponent to render with the new values.
The ObservedGlobalProps type acts as a Pick type, where properties are implicitly picked from GlobalProps. The resulting type is typically merged with any component-specific properties via a union.
See the Observed Global Properties section for more details.
-
Options<P, M extends Record<string, (...args) => any> = {}>
-
The Options type specifies additional options that can be passed when calling registerCustomElement to register a function-based VComponent with the JET framework.
These additional options come into play under certain circumstances:
-
Optional
bindings
metadata (see PropertyBindings for further details) are only honored when the VComponent custom element is used in a Knockout binding environment. -
Optional
contexts
metadata (see Contexts for further details) are only honored when the VComponent is rendered as an intrinsic element in a virtual dom tree.
In the following example:
-
FormFunctionalComponent
is a VComponent implementing a form that consumes 'labelEdge' and 'readonly' properties from its parent container. It also provides values for 'labelEdge' and 'readonly' properties to its children with any necessary transformations. -
The implementation includes an input element, and the parent function-based VComponent exposes
a public
focusInitialInput
method to set the focus on this element as needed.
import { h, Ref } from 'preact'; import { useImperativeHandle, useRef } from 'preact/hooks'; import { forwardRef } from 'preact/compat'; import { registerCustomElement } from 'ojs/ojvcomponent'; type Props = Readonly<{ labelEdge?: 'inside' | 'start' | 'top'; readonly?: boolean; }>; type FormHandle = { // The doclet description appears in the generated API Doc, whereas the // @ojmetadata description appears in the generated component.json file. /** * Sets the focus on the initial <code>FormInput</code> control in this form. * @ojmetadata description 'Sets the focus on this form.' */ focusInitialInput: () => void; }; export const FormFunctionalComponent = registerCustomElement<Props, FormHandle>( 'my-form-functional-component', forwardRef( ({ labelEdge = 'inside', readonly = false }: Props, ref: Ref<FormHandle>) => { const formInputRef = useRef<HTMLInputElement>(null); useImperativeHandle(ref, () => ({ focusInitialInput: () => formInputRef.current?.focus() })); return ( <input ref={formInputRef} readOnly={readonly} ... /> ... ); } ), { bindings: { // Indicate that the component's 'labelEdge' property will consume // the 'containerLabelEdge' variable provided by its parent, as well as // provide the 'labelEdge' property value under different keys and with // different transforms as required for different consumers. labelEdge: { consume: { name: 'containerLabelEdge' }, provide: [ { name: 'containerLabelEdge', default: 'inside' }, { name: 'labelEdge', default: 'inside', transform: { top: 'provided', start: 'provided' } } ] }, // Indicate that the component's 'readonly' property will consume // the 'containerReadonly' variable provided by its parent, as well as // provide the 'readonly' property value under different keys for different // consumers. readonly: { consume: { name: 'containerReadonly' }, provide: [ { name: 'containerReadonly' }, { name: 'readonly' } ] } } } );
- Deprecated:
-
Since Description 16.0.0
Use doclet metadata within the type alias that maps method names to function signatures instead.
Properties:
Name Type Argument Description bindings
ojvcomponent.PropertyBindings<P> <optional>
contexts
Contexts <optional>
methods
ojvcomponent.Methods<M> <optional>
-
Optional
-
PropertyBindings<P>
-
The PropertyBindings type maps function-based VComponent property names to their corresponding PropertyBinding metadata.
Signature:
Partial<Record<keyof P, MetadataTypes.PropertyBinding>>
-
PropertyChanged<T>
-
The PropertyChanged type is used to identify callback properties that notify VComponent consumers of writeback property mutations. Writeback property callbacks must adhere to the naming convention of "on<PropertyName>Changed", where "PropertyName" is the name of the writeback property with the first character converted to upper case:
type Props = { // This is a writeback property value?: string; // This is the corresponding property changed callback onValueChanged?: PropertyChanged<string>; }
See the Writeback Properties section for more details.
Signature:
(value: T) => void
-
ReadOnlyPropertyChanged
-
By default, writeback property mutations can be driven either by the component, typically in response to some user interaction, or by the consumer of the component. In some cases, writeback properties are exclusively mutated by the component itself. Writeback properties that cannot be mutated by the consumer are known as read-only writeback properties. The <oj-input-text>'s rawValue property is an example of such a property.
The ReadOnlyPropertyChanged type is used to identify callback properties that notify VComponent consumers of read-only writeback property mutations. Read-only writeback property callbacks must adhere to the naming convention of "on<PropertyName>Changed", where "PropertyName" is the name of the writeback property with the first character converted to upper case.
Note that, unlike normal writeback properties that are declared by pairing a normal property declaration with a companion callback property, a read-only writeback property is declared solely by its callback property.
Declarations for both forms of writeback properties can be seen below:
type Props = { // The following two fields establish a writeback property // named 'value' value?: string; onValueChanged?: PropertyChanged<string> // The following field establishes a read-only writeback property // named 'rawValue' onRawValueChanged?: ReadOnlyPropertyChanged<string> }
-
Slot
-
The Slot type identifies properties as representing named slot children. This type is an alias for Preact's ComponentChildren type. As such, the value of a slot property can either be embedded directly in a virtual DOM tree or can be passed to Preact's toChildArray.
See Children and Slots for more details.
Signature:
ComponentChildren
-
TemplateSlot<Data extends object>
-
The TemplateSlot type identifies properties as representing named template slot children. Unlike the Slot type, TemplateSlot is a functional type that takes some context and returns the slot children.
See the Template Slots section for more details.
Signature:
(data: Data) => ojvcomponent.Slot