ComponentSet
Every lookup returns a ComponentSet: the components that matched, whether
that's none, one, or twenty. A single match is a set of one, so you only need to
learn one shape.
A set does two things. It's a collection you can count, narrow, and iterate. And it's a shortcut — call a method on the set and it applies to every member.
const cards = experience.findByLocator('card')
cards.count // 3
cards.visibility.show() // shows all three
cards.first() // the first one, as a Component
Narrowing to one component
| Method | Returns | Notes |
|---|---|---|
first() | Component | null | The first match, or null if nothing matched. |
last() | Component | null | The last match, or null. |
nth(i) | Component | null | The match at index i (0-based), or null. |
only() | Component | The single match — throws if the set doesn't hold exactly one. |
only() is the one member of this API that throws, on purpose: use it when
"exactly one thing matched" is an assumption worth catching early.
// you expect one headline — fail loudly if the design changed
experience.findByLocator('headline').only().text.setText('New headline')
// you're not sure anything matched — tolerate zero
experience.findByLocator('promo').first()?.visibility.show()
Working with the collection
| Member | Returns | Notes |
|---|---|---|
count | number | How many matched. |
toArray() | Component[] | The members as a plain array. |
forEach(fn) | void | Run fn for each member. |
map(fn) | R[] | Map members to values. |
filter(pred) | ComponentSet | A new set of the members matching pred. |
ofType(Ctor) | ComponentSet | A new set narrowed to members of a given component class. |
[Symbol.iterator] | — | Sets are iterable: for (const c of set). |
const videos = experience.findByTag('cml-video')
if (videos.count === 0) return
for (const video of videos) video.visibility.show()
const captions = experience
.findByTag('cml-text')
.filter((c) => c.getData('role') === 'caption')
captions.map((c) => c.text.getText())
Calling methods on a set
A set exposes the same capability namespaces as a single component, and passes the call down to its members:
const cards = experience.findByLocator('card')
cards.text.setText('Updated') // every card that holds text
cards.states.toggle('expanded') // flip the state on every card
cards.visibility.hide() // hide them all
cards.setAttribute('data-seen', '1') // every card
How calls fan out
Actions apply to everything; reads use the first match. A method that
changes something runs on every member. A method that returns a value can't
return twenty answers, so it runs on the first member — and if the set held
more than one, it says so once in the console and points you at .first(),
.only(), or .forEach().
Reads include getAttribute, getData, getElement, text.getText,
states.list, states.current, and visibility.isHidden.
const prices = experience.findByLocator('price')
prices.text.setText('$9') // sets all of them
prices.first()?.text.getText() // read one, explicitly
prices.forEach((p) => console.log(p.text.getText())) // or read each
In TypeScript, only the action methods appear on a set's capability namespaces.
Reads are omitted so the compiler nudges you to narrow with .only() or
.first() first. Plain JavaScript will let you read from a set — you'll just get
the console warning.
Nothing matched? Nothing happens. An empty set, a capability none of the
members carry, or a mix of types all degrade to a no-op with one explanatory
console warning. Only only() throws.
// a set with mixed types: the videos play, everything else is skipped
// (with one warning). Narrow first if you'd rather keep the console clean:
experience
.findByLocator('media-row')
.filter((c) => c.has('media'))
.media.play()
See Troubleshooting for what each warning means.
Subscribing on a set
on(type: SdkEventType, handler: (event: ComponentEvent) => void): Unsubscribe
Subscribes every member at once and returns a single function that detaches all of them.
const off = experience.findByLocator('cta').on('component.click', (event) => {
console.log('clicked', event.target.cmlLocator)
})
off() // detaches from every member
The members are captured when you call on — components that appear later
aren't covered. For content that comes and goes, subscribe on a container
instead and let events bubble: see Events.
Building a set yourself
createComponentSet(items: ReadonlyArray<Component>): ComponentSet
Rarely needed, since lookups build sets for you. It's there for when you've assembled components from several searches and want to drive them as one group.
import { createComponentSet } from '@ceros/flex-experience-sdk'
const group = createComponentSet([
...experience.findByLocator('header').toArray(),
...experience.findByLocator('footer').toArray(),
])
group.visibility.hide()