Display data in a list

Display Data in a List

At the end of the previous article, you fetched data from the emulated backend and displayed it as a JSON structure to test the backend. In this article, you will learn how to use data-aware components to display data.

📔

You can continue working with the code you completed in the previous tutorial section. Alternatively, you can download the code from here and carry on.

Fetching Data from API

💡

With XMLUI, fetching data from a REST API is straightforward.

The markup in the previous articles fetched a message with the DataSource component. Often, you use only a URL to access data in a backend.

When using the data property (with any component), the framework infers from its value that it must fetch data (invoking a GET request with the particular URL).

To see how it works, replace the Page tag with the /contacts URL to use the following contents:

Main.xmlui
<!-- Omitted -->
<Page url="/contacts">
  <List data="/api/contacts" />
</Page>
<!-- Omitted -->

List is a component that works with an array of items. The framework fetches the data from the /api/contacts URL and injects it into the List, which displays each item.

If the app is not running yet, start it on your web server and visit the app's page in your browser. The code snippet you have just added displays some contact data when you click the Contacts menu:


Default task list

The List component does not know anything about the semantics and structure of the data. Unless you provide some hints (or a sophisticated description) of the UI to display a particular data item, List will display a simple layout selecting a field as a title and another as a subtitle.

The default layout is good for quick prototyping, but in this case, you need a better way to display fetched data!

Customizing the List

The List component is versatile and provides several services, making it easy to customize the layout. In this section, you will change the outlook of list items. A contact record has these fields (columns):

FieldTypeDescription
idintegerThe unique identifier of the contact
fullNamestringThe contact's name
categoryIdintegerReference to the category ID of the contact
commentsstringAdditional information about the contact
reviewDueDatedateThe date when the contact should be reviewed
reviewCompletedbooleanIndicates whether the review is completed

The default display of the list uses the fullName and then categoryId fields. If you want to change the default view of an item, you can modify the template used to show a particular item. Modify the List component's definition to use a different template:

Main.xmlui
<!-- Omitted -->
<List data="/api/contacts">
  <Card title="{$item.fullName}" subtitle="{$item.comments}" />
</List>
<!-- Omitted -->

The nested markup defines the UI for a single item in the list. The code snippet uses a Card component for a list item. Card is a simple container for displaying cohesive data, such as record fields. This component has a predefined layout we can leverage through the title and subtitle fields.

When processing the data, List uses a special property, $item, to refer to the current iteration item. In the expressions above, $item.fullName and $item.comments are the fields of the current iteration item, of a contact record.


Template with Card

Adding and Formatting Fields

Let's change the layout of the items to something more attractive and helpful. Modify the nested items of List as in the following code snippet:

Main.xmlui
<!-- Omitted -->
<List data="/api/contacts">
  <Card>
    <HStack verticalAlignment="center">
      <Checkbox initialValue="{$item.reviewCompleted}" />
      <VStack width="*" gap="0">
        <Text variant="strong">{$item.fullName}</Text>
        <Text>{$item.comments}</Text>
      </VStack>
      <HStack verticalAlignment="center" horizontalAlignment="end">
        <Text>{smartFormatDate($item.reviewDueDate)}</Text>
      </HStack>
    </HStack>
  </Card>
</List>
<!-- Omitted -->

Here, you add a checkbox indicating the completeness of a particular item. With the help of VStack and HStack layout components, you align the item's title and description in a column with two rows and display the reviewDueDate field aligned right in the item's row. Observe the smartFormatDate($item.reviewDueDate) expression, which smartly transforms a reviewDueDate field, for example, recognizing that a particular date is Yesterday.


Test API
📔

It might occur to you that rendering hundreds or thousands of data items would consume a lot of resources. The List component is a virtualized container that ensures that only data items visible in the current viewport (and a few near the edges) are rendered.

Categorizing Data

In the app's database, each contact can be assigned a category. The available categories are stored in the database with the following structure:

FieldTypeDescription
idintegerThe unique identifier of the category
namestringThe category's name
colorstringThe category's color

XMLUI has a Badge component that can use a color map (name and color pairs). When you pass a value to a Badge, it leverages the color map to set up the component's background according to the badge's text. In this section, you will add a Badge displaying the category associated with a particular contact:

  1. Add a DataSource component to the markup before the NavPanel tag with the categories identifier. This DataSource component will fetch all category data from the backend.
Main.xmlui
<App 
  layout="vertical-sticky" 
  name="Contact List Tutorial" 
  logo="resources/logo.svg" 
  logo-dark="resources/logo-dark.svg">
  <AppHeader>
    <H2>Contact Management</H2>
    <SpaceFiller />
    <ToneChangerButton />
  </AppHeader>
  <DataSource id="categories" url="/api/categories" />
  <!-- Omitted -->
  <NavPanel>
  <!-- Omitted -->
</App>
  1. Create a new Main.xmlui.xs file in the app's root folder. This file intentionally has the same name extended with .xs as the Main.xmlui markup file. It is a code-behind file containing variables and functions visible within the app's main component. A code-behind file represents states, actions, and other utility functionality. In this case, it will declare variables. Copy this content into the new file:
Main.xmlui.xs
// Create a color map for all categories
var categoriesColorMap = toHashObject(categories.value, "name", "color");
 
// Resolve category name by id
function getCategoryNameById(categoryId) {
  const category = findByField(categories.value, "id", categoryId);
  return category ? category.name : "";
}

The categoriesColorMap variable converts the list of topic records into a hash object with the topic name and color value pairs.

The getCategoryNameById looks up the name of a particular category based on its id.

📔

This transformation with the toHashObject JavaScript function converts category records from this format

[
  { "name": "<name1>", "color": "<color1>" },
  { "name": "<name2>", "color": "<color2>" },
  // ...
]

into a hash object:

{
  "<name1>": "<color1>",
  "<name2>": "<color2>",
  // ...
}
  1. Change the definition of Card within the list:
Main.xmlui
<!-- Omitted -->
<List data="/api/contacts">
  <Card var.categoryName="{getCategoryNameById($item.categoryId)}">
    <HStack verticalAlignment="center">
      <Checkbox initialValue="{$item.reviewCompleted}" />
      <VStack width="*" gap="0">
        <Text variant="strong">{$item.fullName}</Text>
        <Text>{$item.comments}</Text>
      </VStack>
      <HStack verticalAlignment="center" horizontalAlignment="end">
        <Badge when="{categoryName}" value="{categoryName}" colorMap="{categoriesColorMap}"/>
        <Text>{smartFormatDate($item.reviewDueDate)}</Text>
      </HStack>
    </HStack>
  </Card>
</List>
<!-- Omitted -->

This code snippet adds a categoryName variable to the Card (this variable's scope is Card and its children). The markup uses this variable as its value will be used in multiple properties within Badge; the variable caches the value. The Badge component uses the categoryName variable's value to display its text with the associated categoriesColorMap (maps each category name to a particular color). Observe the when attribute: if there is no name for the particular contact category, the when value will be false, and the Badge component will not be rendered.

Now, running the app will display the topics of task items:


Badges

In the following article, you will learn to display filtered data.