Repeaters | Lucid.js

Lucid.js / Repeaters

Introduction 🔗

Repeaters are Lucid' way of handling repeated elements, i.e. elements output in a loop. You can 'repeat' normal HTML elements or child components. Each instance of the element/component is fed some data to populate it, if necessary.

Repeaters are processed once the component is rendered, and automatically if they are tied to component data that later changes. They can also be manually reprocessed (for example, if using semi-reactivity).

Syntax 🔗

Repeaters are declared in a component's JavaScript by declaring a repeaters object on the component instance. The object should be a map of selectors to arrays of data objects.

this.repeaters = { '.some #selector': [{foo: 1}, {foo: 2}, {foo: 3}] };

It's possible to retrieve the repeater data used on an output element by inspecting its Lucid.repData (symbol) property i.e. someElement[Lucid.repData].

Example 🔗

Let's see repeaters in action. Let's say you have a component whose HTML template is simply:

<div> <h1>Favourite fruits:</h1> <ul> <li>{{text}}</li> </ul> </div>

We want to repeat the li element, feeding it different text each time. Here's how:

this.repeaters = { li: [{text: 'Apple'}, {text: 'Orange'}, {text: 'Pear'}] };

Selectors work just like CSS selectors. You can be as specific as you like, so rather than just li we could do ul li, .someClass li or whatever. See Selectors.

The result is a list with three items:

<div> <h1>Favourite fruits:</h1> <ul> <li>Apple</li> <li>Orange</li> <li>Pear</li> </ul> </div>

Note that variable placeholders can access deeper level data than just the surface level. This is true for all types of variable replacement in Lucid, not just with repeaters.

Suppose the object structure fed to each li was:

[ ... {object: {text: 'Apple'}} ... ]

The template could access the deeper-level text value via dot syntax, just like in normal JavaScript, i.e.

<li>{{object.text}}</li>

Repeaters and component data 🔗

Repeaters are particularly useful when bound to component data. However when doing so, in order to ensure a reference to the component data is retained, it's necessary to wrap the object in a function. So for example:

<div> <ul> <li>{{$value}}</li> </ul> <button onclick=updateFruit>Update fruit!</button> </div> <script> this.data.fruit = ['orange', 'apple']; this.repeaters = { li: () => this.data.fruit } this.events.updateFruit = () => this.data.fruit = ['orange', 'pear']; </script>

There, we use the fruit array of our component data as the source of repeater output. Later, when the button is clicked, our array is changed, and the repeater updates automatically (assuming we have full-reacitivty; if using semi-reactivity, we can trigger this update by manually reprocessing the repeater.)

Difference vs. other frameworks 🔗

If you're familiar with, say, Vue or React, you'll have noticed that Lucid does iterative output quite differently. Vue would handle iteration via the v-for directive on the HTML tag itself, for example, whereas Lucid does everything via a component's JavaScript.

This illustrates one of Lucid' central philosophies: to not clutter HTML templates with invented syntax.

In Lucid, with the exception of variable placeholders, HTML templates are just that - HTML (albeit with invented tag names representing child component templates).

Repeaters and child components 🔗

Repeaters can be used to iterate child components as well as normal HTML elements.

This works in exactly the same way. Instead of your selector targeting a normal HTML element, point it at your child component tag.

<div> <Somechild foo='{{bar}}' /> </div>

And then in your JS:

this.repeaters = { Somechild: [{bar: 1}, {bar: 2}] };

As with normal HTML elements, selectors pointing to child components can be as specific as you like. So if you had several instances of Somechild and wanted to repeat only the one with id == "justthis", you could do:

this.repeaters = { 'Somechild#foo': [...] }

Or even just:

this.repeaters = { '#foo': [...] };

Using primitives 🔗

In the above example we fed each li an object of data, containing a text property which we ultimately output. Objects are the optimal choice with repeaters because they can store as many properties as are needed for output on the resultant HTML.

But you don't have to use objects with repeaters; it's possible to use primitive values instead. For this, we use the special {{$value}} instruction.

There's also {{$index}}, for outputting the current (zero-indexed) iteration index.

For this, we'd modify the li line of our template to:

<li>{{$value}}</li>

...and modify our repeaters map to use simple string values rather than objects.

this.repeaters = { li: ['Apple', 'Orange', 'Pear'] };

...resulting in the same output as before.

Repeaters without data 🔗

Sometimes you might want to repeat an element or child component without also feeding it data at the same time. In such cases, you can simply specify the number of times the element/component should be repeated, rather than specifying a data array.

this.repeaters = { p: 5 //will result in 5 paragraphs, with no data passed to each };

Reprocessing repeaters 🔗

For fully-reactive apps (see Reactivity), repeaters tied to component data will be automatically be automatically reprocessed as and when that component data changes.

We saw an example of this above, in Repeaters and Component Data.

For semi-reactive apps, though. repeaters can instead be reprocessed via the reprocessReps() method of the components API (or its rr() alias).

Here's how this looks:

<div> <ul> <li>{{$value}}</li> </ul> <button onclick=updateFruit>Update fruit!</button> </div> <script> this.data.fruit = ['orange', 'apple']; this.repeaters = { li: () => this.data.fruit }; this.events.updateFruit = () => { this.data.fruit = ['orange', 'pear']; this.reprocessReps('li'); //or this.rr('li'); }; </script>

Did I help you? Feel free to be amazing and buy me a coffee on Ko-fi!