Gathering detailed insights and metrics for @domx/dataelement
Gathering detailed insights and metrics for @domx/dataelement
npm install @domx/dataelement
Typescript
Module System
Node Version
NPM Version
75.2
Supply Chain
88.2
Quality
75.6
Maintenance
100
Vulnerability
100
License
TypeScript (99.06%)
JavaScript (0.78%)
HTML (0.17%)
Total Downloads
2,588
Last Day
2
Last Week
8
Last Month
35
Last Year
438
8 Stars
552 Commits
1 Forks
1 Watching
20 Branches
2 Contributors
Minified
Minified + Gzipped
Latest Version
1.0.0
Package Id
@domx/dataelement@1.0.0
Unpacked Size
300.29 kB
Size
60.54 kB
File Count
56
NPM Version
7.18.1
Node Version
16.4.2
Cumulative downloads
Total Downloads
Last day
0%
2
Compared to previous day
Last week
0%
8
Compared to previous week
Last month
75%
35
Compared to previous month
Last year
-9.5%
438
Compared to previous year
1
A DataElement
base class with root state support.
Description
Highlights
Installation
Basic Usage
Registering a DataElement
Describing Data Properties
Handling Events and Dispatching Changes
Setting a stateId Property
Using Immer
Using StateChange
Middleware and RDT Logging
The DataElement
base class provides for a Flux/Redux style unidirectional data flow state management
pattern using DOM events and custom elements.
By utilizing the DOM and custom elements, the footprint is small and performance is fast since communication happens through DOM events and not a JavaScript library.
It works well with LitElement
since that also uses custom elements,
but since it is a custom element itself, it will work with any (or no)
library/framework.
See: domxjs.com for more information.
state
property.stateId
property to track state for instance data.functional
JavaScript patterns (e.g. reducers
)
StateChange
also works with Immer which
eliminates object creation fatigue when working with immutable state.1npm install @domx/dataelement
This is a contrived example showing default usage of a DataElement.
1import { DataElement } from "@domx/dataelement"; 2import { customDataElement, event } from "@domx/dataelement/decorators"; 3 4 5export class UserLoggedInEvent extends Event { 6 static eventType = "user-logged-in"; 7 userName:string; 8 fullName:string; 9 constructor(userName:string, fullName:string) { 10 super(UserLoggedInEvent.eventType, { 11 bubbles: true, 12 composed: true 13 }); 14 this.userName = userName; 15 this.fullName = fullName; 16 } 17} 18 19 20@customDataElement("session-data", { 21 eventsListenAt: "window" 22}); 23export class SessionData extends DataElement { 24 static defaultState = { 25 loggedInUserName: "", 26 loggedInUsersFullName: "" 27 }; 28 29 state = SessionData.defaultState; 30 31 // event comes from the EventMap package 32 @event(UserLoggedInEvent.eventType) 33 userLoggedIn(event:UserLoggedInEvent) { 34 this.state = { 35 ...this.state, 36 loggedInUserName: event.userName, 37 loggedInUsersFullName: event.fullName 38 }; 39 this.dispatchEvent(new Event("state-changed")); 40 } 41}
By subclassing the Event class, The
UserLoggedInEvent
acts as a great way to document what events a data element can handle. This is similar to action creators in Redux. They can be defined in the same file as the DataElement (or in a separate file if that works better for you) and used by UI components to trigger events.
The static
defaultState
property allows UI components to reference thedefaultState
for initialization.
The SessionData
element can be used in any UI component.
1import { LitElement, html } from "lit"; 2import { customElement } from "lit/decorators.js"; 3import { linkProp } from "@domx/dataelement"; 4import { SessionData, UserLoggedInEvent } from "./SessionData"; 5 6class LoggedInUser extends LitElement { 7 state = SessionData.defaultState; 8 9 render() { 10 const state = this.state; 11 12 return html` 13 <session-data 14 @state-changed="${linkProp(this, "state")}" 15 ></session-data> 16 <button @click="${this.updateUserClicked}">Update user</button> 17 <div> 18 Logged in as: ${state.loggedInUserName} 19 (${state.loggedInUsersFullName}) 20 </div> 21 `; 22 } 23 24 updateUserClicked(event) { 25 this.dispatchEvent(new UserLoggedInEvent("juser", "Joe User")); 26 } 27}
linkProp is a helper method to propagate changes from a data element to its parent UI element. See linkProp.
There are two ways to register a DataElement.
1import { DataElement } from "@domx/dataelement"; 2import { customDataElement } from "@domx/dataelement/decorators"; 3 4@customDataElement("user-data") 5class UserData extends DataElement { 6 // ... 7}
This will register the custom element with the specified name and will use the specified name in the root state tree.
The name is the name of the custom element.
options
1import { customDataElements, DataElement } from "@domx/dataelement"; 2 3class UserData extends DataElement { 4 static eventsListenAt = "window"; 5 static stateIdProperty = "state"; // this is the default; 6 // ... 7} 8customDataElements.define("user-data", UserData);
The
eventsListenAt
andstateIdProperty
static properties are optional.
The default data property is state
. However, you can set any and multiple
properties to be a data property.
Data properties can be defined using a decorator or by using static properties.
1import { DataElement } from "@domx/dataelement"; 2import { dataProperty } from "@domx/dataelement/decorators"; 3 4class UserData extends DataElement { 5 @dataProperty() 6 user = {}; 7 8 @dataProperty({changeEvent: "session-data-changed"}) 9 sessionData = {}; 10}
The above example sets the
user
property as a data property. The change event monitored will be"user-changed"
.
A second data property here is
sessionData
which specifically defines the change event as"session-data-changed"
.
A static property can also be used to define the data properties.
1import { DataElement } from "@domx/dataelement"; 2 3class UserData extends DataElement { 4 static dataProperties = { 5 user: {}, // user-changed is the implied change event 6 sessionData: {changeEvent: "session-data-changed"} 7 }; 8 9 user = {}; 10 sessionData = {}; 11}
The DataElement uses EventMap for declarative event changes.
After making changes to a data property, a change event will need to be triggered on the data element.
This can be done by calling this.dispatchEvent(new Event("state-change"))
where state-change
is the name of the change event. Or for convenience, you can call the dispatchChange()
method.
dispatchChange(prop = "state")
1import { DataElement } from "@domx/dataelement"; 2import { customDataElement, dataProperty, event } from "@domx/dataelement/decorators"; 3 4@customDataElement("user-data") 5class UserData extends DataElement { 6 7 @dataProperty() 8 user = { 9 userName: "unknown", 10 fullName: "unknown", 11 }; 12 13 @event("fullname-updated") 14 userUpdated({detail:{fullName}}) { 15 this.state = { 16 ...this.state, 17 fullName 18 }; 19 this.dispatchChange("user"); 20 } 21 22 @event("username-updated") 23 userUpdated({detail:{userName}}) { 24 this.dispatchChange("user", { 25 ...this.state, 26 userName 27 }); 28 } 29}
Note: the
username-updated
event handler passes the state as a second parameter; when doing this, it does a quick comparison between the existing and new state and only dispatches the changes if they differ.
Using a stateId enables having multiple instances of the same data element in the DOM that keep track of state per instance.
The stateId property name can be defined either by setting a static property on the class or with the
customDataElement
decorator.
The default stateIdProperty is "stateId". If this property has a value then the state will be stored in its own slot in the root state tree.
1import { DataElement } from "@domx/dataelement"; 2import { customDataElement } from "@domx/dataelement/decorators"; 3 4@customDataElement("user-data", { 5 stateIdProperty: "userId" 6}) 7class UserData extends DataElement { 8 userId = null; 9 //... 10}
1import { customDataElements, DataElement } from "@domx/dataelement"; 2 3class UserData extends DataElement { 4 static stateIdProperty = "userId"; 5 6 userId = null; 7 //... 8} 9customDataElements.define("user-data", UserData);
Most of the time, it is recommended that each instance has its own data element. For example, if the user is navigating a list of items, for each item that is open, it would have its own UI element and data element.
However, re-using the same data element for different instances is possible. But there is a little extra work to be done to keep the state in sync.
This can be done by calling refreshState()
on the DataElement.
In some cases, the stateId may be fed by a DOM attribute.
If that attribute changes, or internally the stateId
property changes,
then the internal state will need to be refreshed.
1import { DataElement } from "@domx/dataelement"; 2import { customDataElement, dataProperty } from "@domx/dataelement/decorators"; 3 4@customDataElement("user-data", {stateIdProperty: "userId"}) 5class UserData extends DataElement { 6 7 static get observedAttributes() { return ["user-id"]; } 8 static defaultState = { userName: "unknown" }; 9 10 // tying the userId property to the user-id attribute 11 get userId():string { return this.getAttribute("user-id") || ""; } 12 set userId(stateId:string) { this.setAttribute("user-id", stateId); } 13 14 @dataProperty(); 15 user = UserData.defaultState; 16 17 attributeChangedCallback(name:string, oldValue:string, newValue:string) { 18 if (name === "user-id") { 19 // the user-id is changing so we need to 20 // refresh the state with the default state 21 this.refreshState({ 22 user: UserData.defaultState 23 }); 24 } 25 } 26}
The static
observedAttributes
property and theattributeChangedCallback
method are part of the custom element definition. See Using Custom Elements.
Working with immutable state can cause extra work to make sure all of the changes are propagated correctly so that they can be identified and correctly updated by UI components.
Immer is a great library that can remove the need to perform much of that overhead.
1import { DataElement } from "@domx/dataelement"; 2import { customDataElement, event } from "@domx/dataelement/decorators"; 3import { produce } from "immer"; 4 5@customDataElement("user-data") 6class UserData extends DataElement { 7 8 state = { 9 fullName: "unknown" 10 }; 11 12 @event("user-updated") 13 userUpdated({detail:{fullName}}) { 14 // Immers produce method takes care of update the 15 // immutable state correctly 16 this.state = produce(this.state, (state) => { 17 state.fullName = fullName; 18 }; 19 this.dispatchChange(); 20 } 21}
This example is very simple but, in many cases, changing multiple parts of the state object and adding to, or removing items from Arrays can be greatly simplified by using Immer.
StateChange
is another great option for updating state and
it can also be configured to use Immer.
In addition, using StateChange
provides for more granular logging updates
including pushing to Redux Dev Tools so every update is recorded.
StateChange can provide for a pattern similar to Redux reducers (which is up to you),
but it also enables re-using state updates among different events. It also
supports asynchronous changes using a tap
method.
See the StateChange repository for more information.
StateChange
is included as an DataElement export.
1import { StateChange } from "@domx/dataelement"; 2import { applyImmerToStateChange } from "@domx/dataelement/middleware"; 3import { customDataElement, event } from "@domx/dataelement/decorators"; 4 5// this can be called just once for the entire application, 6// so it can be added in a root page. 7applyImmerToStateChange(); 8 9@customDataElement("user-data") 10class UserData extends DataElement { 11 12 state = { 13 fullName: "unknown" 14 }; 15 16 @event("user-updated") 17 userUpdated({detail:{fullName}}) { 18 StateChange.of(this) 19 .next(updateFullName(fullName)) 20 .dispatch(); 21 } 22} 23 24const updateFullName = fullName => state => { 25 state.fullName = fullName 26};
DataElement
exposes middleware to hook into both the connectedCallback
and
disconnectedCallback
methods.
There is also a function available to apply Redux dev tool logging.
Logs change events, and if using StateChange
, logs state snapshots
with each next
call.
1import {applyDataElementRdtLogging} from "@domx/DataElement/middleware"; 2 3applyDataElementRdtLogging();
options
StateChange
and do not want the additional
change event logged.This can be used alongside
RootState.snapshot(name)
to create a named snapshot in Redux Dev Tools.
1import {DataElement} from "@domx/DataElement";
2
3const connectedCallback = (metaData:DataElementMetaData) => (next:Function) => () => {
4 // add custom behaviors and call next
5 next();
6};
7
8const disconnectedCallback = (metaData:DataElementMetaData) => (next:Function) => () => {
9 // add custom behaviors and call next
10 next();
11};
12
13DataElement.applyMiddlware(connectedCallback, disconnectedCallback);
14
15// removes all middelware methods
16DataElement.clearMiddleware();
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
Found 0/12 approved changesets -- score normalized to 0
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
license file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
19 existing vulnerabilities detected
Details
Score
Last Scanned on 2025-01-27
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More