Skip to main content

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

MethodReturnsNotes
first()Component | nullThe first match, or null if nothing matched.
last()Component | nullThe last match, or null.
nth(i)Component | nullThe match at index i (0-based), or null.
only()ComponentThe 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

MemberReturnsNotes
countnumberHow many matched.
toArray()Component[]The members as a plain array.
forEach(fn)voidRun fn for each member.
map(fn)R[]Map members to values.
filter(pred)ComponentSetA new set of the members matching pred.
ofType(Ctor)ComponentSetA 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
TypeScript

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()