A few polishing touches

A Few Finishing Touches

There are a few things left to improve the user experience in our app. In this section, you will make minor changes to enhance the app's look and feel.

đź“”

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 the Number of Records

When the app starts, it takes a fraction of a second to reach the backend and retrieve the number of contact records to display. You may still see an "(undefined)" label while the data loads. We can quickly fix this by showing a "loading..." message while the contact data is retrieved from the database.

Create a code-behind file for Main.xmlui (remember, you must name it Main.xmlui.xs). Add a new function, countLabel to Main.xmlui.xs:

Main.xmlui.xs
function countLabel(cat) { 
  return contactCounts.inProgress ? "loading..." : contactCounts.value[cat];
}

This function takes a name (the name of the contact section) as its argument and retrieves the corresponding number of contact categories. The "true" value of the contactCounts.inProgress property indicates whether the DataSource (with the contactCounts id) retrieving the contact counts is currently fetching data.

Update the task count expression in the menu item labels to leverage the new, improved display:

Main.xmlui
<!-- Omitted -->
<NavPanel>
  <NavLink label="Dashboard" to="/" />
  <NavLink label="All Contacts ({countLabel('all')})" to="/contacts/all" />
  <NavLink label="Overdue Reviews ({countLabel('overdue')})" to="/contacts/overdue"/>
  <NavLink label="Today's Reviews ({countLabel('today')})" to="/contacts/today"/>
  <NavLink label="Upcoming Reviews ({countLabel('upcoming')})" to="/contacts/upcoming"/>
  <NavLink label="Completed Reviews ({countLabel('completed')})" to="/contacts/completed"/>
</NavPanel>
<!-- Omitted -->

Now, instead of "undefined", labels display "loading...":


Loading message

Displaying List Sections

The List component supports the grouping of list items according to a specified attribute. It has a groupBy property that enables this grouping. The List identifies each unique value of the attribute given to groupBy and groups the items accordingly.

In the previous section, you created a getSections function within Main.xmlui.xs. Let's use the same function in the Contact.xmlui.xs code-behind file; append the definition of getSections and a few other functions to it:

Contacts.xmlui.xs
function getSection(contact) {
  if (contact.reviewCompleted) return "Completed";
  if (!contact.reviewDueDate) return "No Due Date";
  if (isToday(contact.reviewDueDate)) return "Today";
  return getDate(contact.reviewDueDate) < getDate() ? "Overdue" : "Upcoming";
}
 
function extendWithSection(contact) {
  return { ...contact, section: getSection(contact) };
}
 
function loading(contacts, message) {
  return contacts.inProgress ? "loading..." : message;
}

The extendWithSection function takes a particular contact and adds a new property named section. If you do not precisely understand the { ...contact, section: getSection(contact) } expression, just accept that it does its job.

The loading function displays the "loading..." text while the data is fetched from the backend. When the data is received, it shows the message passed.

To display the section information, add the groupBy property to the List and define the template to display section headers:

Contacts.xmlui
<Component name="Contacts">
  <DataSource id="categories" url="/api/categories" />
  <List data="{$props.data.map(extendWithSection)}"
    groupBy="section">
    <property name="groupHeaderTemplate">
      <HStack paddingHorizontal="$space-normal" paddingVertical="$space-tight">
        <Text 
          variant="subtitle"
          value="{$group.key} ({loading(contacts, $group.items.length ?? 0)})" />
      </HStack>
    </property>
    <!-- Unchanged -->
  </List>
</Component>

The list now uses the contact records extended with the section property (mapping the contacts with the extendWithSection function).

In this code snippet, the groupBy property of List names the list's property for grouping items; it is the newly calculated section property of a contact record.

The highlighted groupHeaderTemplate area is the markup that represents the header of a particular item group. The list passes the special $group context property to the template. Its value is an object with key holding the grouping value; items store the items within the particular group.

Now, When you display all contacts, you can see that items are sectioned:


Sectioned items
đź“”

When you do not declare a group template, the items are still grouped, but the list does not indicate the group boundaries. Besides groupHeaderTemplate, List provides a groupFooterTemplate. With this property, you can declare a markup for the footer of a section.

Expanding and Collapsing Sections

The sections of a list can be expanded or collapsed. Unless you declare otherwise, all sections are expanded by default. Using the $group context property, you can query the state of the section (isExpanded) and use the toggle function to expand or collapse the section.

Update the List definition in Contacts:

Contacts.xmlui
<Component name="Contacts">
  <DataSource id="categories" url="/api/categories" />
  <List data="{$props.data.map(extendWithSection)}"
    groupBy="section">
    <property name="groupHeaderTemplate">
      <HStack 
        paddingHorizontal="$space-normal" 
        paddingVertical="$space-tight"
        verticalAlignment="center"
        onClick="$group.toggle">
          <Text 
            variant="subtitle" 
            value="{$group.key} ({loading(contacts, $group.items.length ?? 0)})" />
        <SpaceFiller />
        <Icon 
          name="{$group.isExpanded ? 'chevrondown' : 'chevronright'}" 
          size="md" />  
      </HStack>
    </property>
    <!-- Unchanged -->
  </List>
</Component>

The paddingHorizontal and paddingVertical properties of HStack (XMLUI calls them layout properties) define some spacing around the content within HStack. The $space-normal and $space-tight layout values refer to theme settings (they come from the app's current theme). Using these values ensures that your app accommodates the theme changes.

Now, you can expand or collapse sections by clicking them. The following figure shows the Completed section in its collapsed state:


collapsed sections

Section Ordering

The order of sections displayed above could be more helpful using a different ordering. With the help of the defaultGroups property of the List, you can define the order of sections.

Modify the List accordingly:

Contacts.xmlui
<!-- Omitted -->
<List 
  items="{contacts.value.map(t => ({...t, section: getSection(t)}))}"
  defaultGroups="{['Overdue', 'Today', 'Upcoming', 'No Due Date', 'Completed']}"
  groupBy="section">
  <!-- Omitted -->
</List>
<!-- Omitted -->

You can see the updated section order:


Sections ordered

When you visit a filtered page, only the corresponding section is displayed, like in the following figure:


no empty sections

So far, you have displayed only read-only data. In the following section, you will create, edit, and delete contact data.