Skip to content

Data & Templates

Datasources

A datasource supplies a component with its content at render time instead of the author placing that content by hand. The point is to decouple a component from where its data comes from: a view is written once against the component’s data interface, and a datasource feeds it whatever data you want, built by Java or pulled from an OSGi service. Any datasource can feed a given component as long as it implements that component’s data interface, so you can switch the data behind a view generically without touching the view.

What a datasource is

A datasource lets a component (a Card List, a Table, a Card, a Button, a Text block, and so on) get its content from Java or from an OSGi service instead of from child nodes an author placed by hand. Because a component’s view only knows the component’s data interface, any datasource that implements that interface can feed it, and you switch which one is used per instance. It is made of two parts: a Sling Model that produces the data, and a registration node that binds that model to a target component. An author selects a registered datasource on a component instance.

How the pieces fit, in order:

  1. Write the model. A Sling Model produces the rows (below).
  2. Register it. A node under the component’s datasources/ folder points classPath at your model (below).
  3. An author selects it. On a component instance they set kes:datasource to the registration node’s name (below).
  4. The view renders it. A framework view uses the built-in CardListDataSourceComponent, which reads the selection and exposes the built cards (below).
A custom component does not require a datasource. Reach for one only when a list component should render content sourced from a service rather than authored inline.

The pattern below is what the Kestros 0.9.0 Maven archetype scaffolds, and it matches the Card List datasources in the Kestros sample sites: a datasource model, a registration node under the target component, and the filter.xml coverage to deploy it.

The datasource model

The model is a Sling Model. The Maven archetype scaffolds it in the application module (alongside the components it feeds), under the package <groupId>.application.datasources. A datasource model can live in any Java module that depends on the component libraries; the archetype keeps it in application so the core module stays free of component dependencies. The package in the example below is illustrative. Extend the base class that matches the target component and implement that component's interface. For a Card List, extend BaseContainerSlingModelDataSource and implement KestrosCardList. Pull your data from an OSGi service and return the component's elements:

SampleCardListDataSource.java
@Model(adaptables = {SlingHttpServletRequest.class, Resource.class})
public class SampleCardListDataSource extends BaseContainerSlingModelDataSource
    implements KestrosCardList {

  @OSGiService @Optional
  private SampleService sampleService;

  @Override
  public List<KestrosCard> getCardElements() {
    List<KestrosCard> cards = new ArrayList<>();
    if (sampleService == null) {
      return cards;                          // service optional: render nothing
    }
    for (Map<String, String> row : sampleService.getRows()) {
      cards.add(new SampleCard(row.get("title"), row.get("href"), this)); // build with KestrosCardImpl (see "Building the container's elements" below)
    }
    return cards;
  }
}

Here SampleCard stands in for a KestrosCard element you build inside the datasource. Building the container’s elements below shows how to construct that element with the matching KestrosCardImpl class.

The registration node

The registration node is placed under the target component's datasources/ folder, overlaying the out-of-the-box component tree. It names your model class through the classPath property. For the Card List the node path is:

/apps/kestros/commons/components/lists/card-list/datasources/my-cards

datasources/my-cards/.content.xml
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="nt:unstructured"
    jcr:title="My Cards" group="My Project"
    classPath="com.example.mysite.application.datasources.SampleCardListDataSource"
    container="{Boolean}true"/>
Req: Y = required, Rec = recommended, blank = optional
PropertyTypeReqDescription
jcr:titleStringRecName shown in the datasource picker
classPathStringYFully-qualified name of the datasource model class
groupStringGroup the datasource is listed under in the picker
containerBooleanSet true for a container component such as the Card List
FileVault intermediate-folder gotcha. If you ship this overlay as a FileVault content package, and it lives under the out-of-the-box /apps/kestros/commons/components tree, your package must (a) ship a sling:Folder .content.xml for every intermediate folder it introduces (lists/, card-list/, datasources/) and (b) list those ancestor paths in filter.xml as explicit <include> patterns. Cover only the leaf and FileVault skips the intermediates: the build still reports success while the node returns a 404. Some Kestros sample projects instead deliver these nodes as bundle initial content, which avoids the FileVault packaging step entirely.

Selecting a datasource on a component

An author binds a registered datasource to a component instance by setting kes:datasource to the registration node name on the authored component:

component instance .content.xml
<my-cards jcr:primaryType="nt:unstructured"
    sling:resourceType="/libs/kestros/commons/components/lists/card-list"
    kes:datasource="my-cards"/>
If kes:datasource is not set, the Card List has no rows to render until an author selects a datasource.

Building the container’s elements

A container component (a Card List, a Table, a Button Group, and so on) does not take raw strings from its datasource: it takes typed element objects. You build those in the datasource with the matching Kestros…Impl class. Every one of those Impl constructors ends with the same three arguments: the datasource itself (this), a resource-name prefix, and a node name; the arguments before them carry the element’s data.

Card List: getCardElements() returns List<KestrosCard>
cards.add(new KestrosCardImpl(
    sampleModel.getDescription(),                                  // body text
    new KestrosHeadingImpl(sampleModel.getTitle(), "h3", this, "title", "titleElement"),
    null, null,                                                    // image, button group (optional)
    this, "card", "sample-card-" + index));
Table: getHeaderElements() + getRowElements()
// a header cell
headers.add(new KestrosTableHeaderImpl("Name", this, "header", "sample-header-name"));

// a row: build its cells, then wrap them in a row
List<KestrosTableCell> cells = new ArrayList<>();
cells.add(new KestrosTableCellImpl("Ada Lovelace", this, "cell", "sample-cell-0-0"));
rows.add(new KestrosTableRowImpl(cells, this, "row", "sample-row-0"));
Button Group: build buttons, then the group
List<KestrosButton> buttons = new ArrayList<>();
buttons.add(new KestrosButtonImpl(
    "Get Started", "/get-started",                                // text, href
    "Get started", AnchorTarget.SAME_WINDOW, null,             // title, target, rel
    null, null, null, false,                                    // aria-label, aria-describedby, lang, disabled
    this, "button", "sample-button-0"));
KestrosButtonGroup group = new KestrosButtonGroupImpl(buttons, this, "button-group", "sample-group");

Rendering the datasource in a framework view

A component view renders a Card List by using the built-in CardListDataSourceComponent model. That model reads the instance's kes:datasource selection, resolves the matching registration node, instantiates the datasource class named by classPath, and exposes the built cards as cards. The view iterates them without needing to know the concrete datasource class:

content.html
<sly data-sly-use.cardList="${'io.kestros.cms.components.basic.core.lists.cardlist.CardListDataSourceComponent'}"/>
<div data-sly-list.card="${cardList.cards}"><sly data-sly-resource="${card}"/></div>

Placed at /apps/kestros/commons/components/lists/card-list/<framework-code>/…/content.html, this view renders whichever datasource the author selected, styled for that framework. See Custom Components and the Glossary for related concepts.

A view never references a datasource class directly. Every card-list view, in every framework and every version, binds the same CardListDataSourceComponent, which reads the author’s kes:datasource selection and resolves it. A framework’s or a version’s view differs only in its markup; how it gets its data is identical to the common view above. Pointing data-sly-use at a concrete datasource class (bypassing kes:datasource) is not how Kestros datasources work, so do not do it.