Create a Dashboard
Though you created a Dashboard page when starting with this tutorial, that page is still empty. It is time to create a dashboard with some information and charts.
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.
A Rudimentary Dashboard
The dashboard will display indicative numbers of significant review counts. These indicators will be housed in interactive cards. By clicking on a card, the app will navigate to the list of the corresponding contacts.
Create a new file, Dashboard.xmlui
within the components
folder:
<Component name="Dashboard">
<DataSource id="contactCounts" url="/api/contactcounts" />
<FlowLayout var.counts="{contactCounts.value}">
<H2>Overdue Reviews: {counts.overdue}</H2>
<H2>Today's Reviews: {counts.today}</H2>
<H2>Upcoming Reviews: {counts.upcoming}</H2>
<H2>Completed Reviews: {counts.completed}</H2>
</FlowLayout>
</Component>
The Dashboard
component uses a DataSource
with the /api/contactcounts
endpoint (you used it earlier) to get some counter values to display. The counts
variable allows referencing the individual counters shorter, for example, using counts.overdue
instead of contactCounts.value.overdue
.
Use this new component instead of the placeholder text within Main.xmlui
:
<App layout="vertical-full-header">
<!-- Omitted -->
<Pages>
<Page url="/">
<Dashboard />
</Page>
<!-- Omitted -->
</App>
Earlier, you created a DataSource
with the contactCounts
ID, which retrieves several counters in an object. The dashboard displays this data.
When you navigate to the Dashboard page, it displays a some numeric information:

Cards with Numbers
This user interface needs to look more exciting and helpful.
Your next task will be to create a card that highlights useful numerical information.
Start with the InfoCard
component by adding the InfoCard.xmlui
file to the components
folder with the following code:
<Component name="InfoCard">
<Card height="{$props.height}">
<Text variant="subtitle" value="{$props.title}"/>
<CVStack width="100%">
<H2 color="{$props.color}" fontSize="4em">
{$props.isLoading ? "..." : $props.value}
</H2>
</CVStack>
</Card>
</Component>
InforCard
allows you to set its title text, color, and number to display. It also receives an isLoading
property to display "..." while the counter data is being fetched from the backend.
Change the dashboard to use the InfoCard
component:
<Component name="Dashboard">
<DataSource id="contactCounts" url="/api/contactcounts" />
<FlowLayout
var.counts="{contactCounts.value}"
var.isLoading="{contactCounts.inProgress}">
<InfoCard
width="25%" title="Reviews Overdue"
isLoading="{isLoading}"
value="{counts.overdue}"
color="$color-danger" />
<InfoCard
width="25%"
title="Today's Reviews"
isLoading="{isLoading}"
value="{counts.today}"
color="$color-warning" />
<InfoCard
width="25%"
title="Upcoming Reviews"
isLoading="{isLoading}"
value="{counts.upcoming}" />
<InfoCard
width="25%"
title="Completed Reviews"
isLoading="{isLoading}"
value="{counts.completed}"
color="$color-valid" />
</FlowLayout>
</Component>
Each InfoCard
displays its associated value. The FlowLayout
component and setting a width of 25% ensures items placed inside will use 25% of the available width of available space:

Adding Navigation
The dashboard is just a summary of essential numbers. To let the user investiage what is behind the number, you need to let them navigate to the list of corresponding contacts.
Prepare the InfoCard
component to receive a link
property and navigate to that link when it is clicked:
<Component name="InfoCard">
<Card
height="{$props.height}"
onClick="{Actions.navigate('/contacts/' + $props.link)}">
<Text variant="subtitle" value="{$props.title}"/>
<CVStack width="100%">
<H2 color="{$props.color}" fontSize="4em">
{$props.isLoading ? "..." : $props.value}
</H2>
</CVStack>
</Card>
</Component>
The Actions.navigate
method visits the specified link. Remember, the <NavPanel>
section of Main.xmlui
contains the links in the menu; all of them start with /contacts
.
Set the link
property's value for each InfoCard
on the dashboard:
<Component name="Dashboard">
<DataSource id="contactCounts" url="/api/contactcounts" />
<FlowLayout
var.counts="{contactCounts.value}"
var.isLoading="{contactCounts.inProgress}">
<InfoCard
width="25%" title="Reviews Overdue"
isLoading="{isLoading}"
value="{counts.overdue}"
link="overdue"
color="$color-danger" />
<InfoCard
width="25%"
title="Today's Reviews"
isLoading="{isLoading}"
value="{counts.today}"
link="today"
color="$color-warning" />
<InfoCard
width="25%"
title="Upcoming Reviews"
isLoading="{isLoading}"
link="upcoming"
value="{counts.upcoming}" />
<InfoCard
width="25%"
title="Completed Reviews"
isLoading="{isLoading}"
value="{counts.completed}"
link="completed"
color="$color-valid" />
</FlowLayout>
</Component>
When you click a dashboard item, the UI logic will take you to the corresponding contact list.
Recent Contacts
It would be great to see the recently added contacts on the dashboard. In this section, you will add a table displaying the last five contacts added in descending order.
Update the Dashboard.xmlui
file by appending a new Card
component after the last InfoCard
:
<Component
name="Dashboard"
var.counts="{$props.contactCountsSource.value}"
var.isLoading="{$props.contactCountsSource.inProgress}">
<!-- Unchanged -->
<Card width="50%" height="350px">
<H4>Recent Contacts</H4>
<Table data="/api/contacts-recent" height="*">
<Column header="Category" width="80px">
<Text>{$item.categoryId}</Text>
</Column>
<Column header="FullName" width="*">
<Text maxLines="1">{$item.fullName}</Text>
</Column>
</Table>
</Card>
</FlowLayout>
</VStack>
</Component>
This code renders a Card
(the same layout container type used within InfoCard
) and a Table
bound to the GET /api/contacts-recent
endpoint.
The Table
has two column definition items (Column
) to name the columns to display in the table:

Category IDs do not give you a hint about a particular category. It would be great if the first column displayed the category's name with a color badge as in the contact list.
Create a new component, CategoryBadge.xmlui
(within the components
folder):
<Component
name="CategoryBadge"
var.categoryName="{getCategoryNameById($props.categoryId)}">
<DataSource id="categories" url="/api/categories" />
<Badge when="{categoryName}" value="{categoryName}" colorMap="{categoriesColorMap}"/>
</Component>
Add a code-behind file, CategoryBadge.xmlui.xs
, to the new component using the similar technique you learned in the Adding a Badge section:
// 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 : "";
}
Change the Text
element within the first Columnn
to the new CategoryBadge
element and update the width of the Category column:
<Component name="Dashboard">
<DataSource id="contactCounts" url="/api/contactcounts" />
<FlowLayout
var.counts="{contactCounts.value}"
var.isLoading="{contactCounts.inProgress}">
<!-- Unchanged -->
<Card width="50%" height="350px">
<H4>Recent Contacts</H4>
<Table data="/api/contacts-recent" height="*">
<Column header="Category" width="200px">
<CategoryBadge categoryId="{$item.categoryId}" />
</Column>
<Column header="FullName" width="*">
<Text maxLines="1">{$item.fullName}</Text>
</Column>
</Table>
</Card>
</FlowLayout>
</Component>

The new CategoryBadge
component can replace the previous markup to display the category in Contact.xmlui
. Change Badge
to CategoryBadge
:
<Component name="Contacts">
<ContactForm id="contactForm" />
<DataSource id="categories" url="/api/categories" />
<VStack>
<HStack horizontalAlignment="end">
<Button icon="plus" onClick="contactForm.open()">New Contact</Button>
</HStack>
<List data="{$props.data.map(extendWithSection)}"
defaultGroups="{['Overdue', 'Today', 'Upcoming', 'No Due Date', 'Completed']}"
groupBy="section">
<!-- Unchanged -->
<Card var.categoryName="{getCategoryNameById($item.categoryId)}">
<!-- Unchanged -->
<HStack verticalAlignment="center" horizontalAlignment="end">
<CategoryBadge categoryId="{$item.categoryId}" />
<Text>{smartFormatDate($item.reviewDueDate)}</Text>
</HStack>
<!-- Unchanged -->
</Card>
</List>
</VStack>
</Component>
You do not need the categories
DataSource in Contacts
, as ContactRow
no longer uses it.
You can remove it from Contacts.xmlui
.
Conclusion
This concludes the step-by-step tutorial.
See the How to section for shorter, concise code snippets showcasing common UI building patterns. To see the basic use case of different components found in XMLUI, see the Basic Components article.
You can download the code of the completed tutorial from here.