Elements
Elements are custom HTML tags that are used to define and create complex components within layouts. All framework elements are UI components, designed to be highly reusable and customisable.
However, elements cannot be directly integrated into components or displays. They provide specific UI functionality and behavior and are intended to be combined and reused within layouts to create more complex components.
The extensibility of elements makes them powerful and flexible. Developers can create custom elements with specific behaviours and functionality to create highly specialised components tailored to specific use cases.
Elements are a key part of the Unless Component Framework's component architecture, providing a flexible and extensible foundation for creating powerful and reusable UI components that can be easily integrated into layouts and components.
Take a look at the list of elements that are provided by our open-source framework: https://bit.cloud/unless/component-library/element/body.
Example
import {Element} from '@unless/component-library.element.element'
import {html, TemplateResult} from 'lit'
export class MyElement extends Element {
static from = () => new MyElement().render()
public render(): TemplateResult | TemplateResult[] {
return html`<p>Hello world!</p>`
}
}
Class methods
Elements are simple components and have only two methods that must be implemented.
Scope | Method | Arguments | Description |
---|---|---|---|
public | render() | none | Renders the element HTML. |
static | from() | none | Creates an instance and renders the element HTML. If the element doesn't use settings, no arguments are needed for this method. |
Using props with elements
At times, Elements
require certain props to be passed in. To ensure consistency, the interface must conform to ElementProps
. Here's an example of an Element
that receives some props:
import {
Element,
ElementProps,
DefaultElementSettings
} from '@unless/component-library.element.element'
interface CloseBtnProps extends ElementProps {
shadowRoot: ShadowRoot
}
export class CloseBtn extends Element {
private shadowRoot: ShadowRoot
private handleClose() {
this
.shadowRoot
.querySelector<HTMLDialogElement>('dialog')?
.close()
}
constructor(props: CloseBtnProps) {
super(DefaultElementSettings)
this.shadowRoot = props.shadowRoot
}
public render() {
...
}
}
Constructor implementation with props
When props are included, it is necessary to implement the constructor. In this case, the Element
has no settings to be passed to the constructor of the parent class, and therefore we can use DefaultElementSettings
.
Consider using settings when possible
It's recommended to use props only for dynamic elements or when the passed value must be by reference, like the ShadowRoot
in this example. If possible, settings should be used instead so that the values can be easily edited in the editor.
Using settings with elements
Settings for Elements
are typically not provided and are optional because Elements
are most often simple. However, for more complex applications, it might be necessary to add settings.
For example, if we want to create a Body Element
, settings might include the content text.
import {
BaseSettings,
TextSetting
} from '@unless/component-library.utils.types'
export interface BodySettings extends BaseSettings {
content: TextSetting
}
/* esbuild-ignore */ // <-- See documentation on settings
import {BodySettings} from './settings-model'
import {Subset} from '@unless/component-library.utils.types'
import {deepCopy, getSettings} from '@unless/component-library.utils.helpers'
export const bodyCopyWith =
(...settingsChanges: Subset<BodySettings>[]) =>
getSettings(deepCopy(settings), ...settingsChanges)
const settings: BodySettings = {
content: {
label: 'content',
type: 'text',
value: '',
placeholder: '',
helpText: 'Sets the content of the body',
}
}
export default settings
import {
Element,
ElementSettingsProps
} from '@unless/component-library.element.element'
import {BodySettings} from './settings/settings-model'
import {deepCopy} from '@unless/component-library.utils.helpers'
import settings from './settings/settings'
import {BodySettings} from "./settings/settings-model";
interface BodyProps extends ElementSettingsProps<BodySettings> {}
export class Body extends Element<BodySettings> {
static from = (props: BodyProps) => new Body(props).render()
static defaultSettings = () => deepCopy(settings)
constructor(props: BodyProps) {
super(props.settings)
}
public render() {
return html`<p>${this.settings.content.value}</p>`
}
}
Class methods with settings
When using settings with elements, it is important to follow a few conventions for consistency.
Props type
To maintain consistency with other layers, an interface must be defined for the constructor props that conforms to ElementWithSettings<TSettings>
which contains at least one field: settings
. For example, the BodyProps
type can be defined as:
import {BodySettings} from "./settings/settings-model";
interface BodyProps extends ElementSettingsProps<BodySettings> {}
Therefore, sub-elements that extend Element
must have a constructor implemented as follows:
class Body extends Element<BodySettings> {
constructor(props: BodyProps) {
super(props.settings)
}
}
Public methods
The following public method must be implemented for elements:
Method | Arguments | Description |
---|---|---|
public render() | none | Renders the element HTML |
Updated over 1 year ago