dot CMS

How to Use dotCMS Dynamic Analytics Displays to Show the Most- and Least-Popular Content on Your Site

How to Use dotCMS Dynamic Analytics Displays  to Show the Most- and Least-Popular Content on Your Site
Freddy

Freddy Rodriguez

Senior Software Engineer

Share this article on:

Content Analytics is a set of robust capabilities offered by dotCMS to track user interactions across a single site, or different sites if you’re leveraging a multi-site architecture. It records user activity in a site — like the number of times content, pages, or files are accessed — providing valuable insights into customer engagement and content performance. This data helps inform strategic decisions to optimize user experience and maximize impact.

In this tutorial, we’re going to use Analytics to provide a list of the most- and least-popular content in a site. Let’s get started!

Step 1: Create a New Widget Content Type

Go to Content Types, click the + (plus) button, and choose the Widget option.

create_new_widget_content_type.png

Let's call it “Popular Content.”

create_widget_content-type_2.png

Step 2: Add a Radio Field

You'll see four fields already present. Let's add another:

  • Add a radio field named “List by”:

    • Mark it as required.

    • Set the following values:

      Less Popular Content | asc

      More Popular Content | desc

    • Set the default value to desc

Step 3: Add Velocity Code

Edit the Widget Code field and paste the following code:

#if ($filterByContentTypeName) 
    #set ($filter = "contentTypeName equals $!filterByContentTypeName")
#else 
    #set ($filter = "")
#end

#set($collection = $analytics.runReport("count", "identifier, title",
    "", $filter, "count $listBy", 5, 0).getResults())

<div>
    <h1>$widgetTitle</h1>

    <table class="table-container">
        <thead>
            <tr>
                <th>Identifier</th> 
                <th>Title</th> 
                <th>Count</th>  
            </tr>
        </thead>

        <tbody> 
            #foreach($item in $collection)
                <tr>
                    <th>$item.get("request.identifier").get()</th> 
                    <th>$item.get("request.title").get()</th> 
                    <th>$item.get("request.count").get()</th>  
                </tr>
            #end
        </tbody>
    </table>  
</div>

Here’s a step-by-step breakdown of what each part of this code sample does:

Filter Setup

#if ($filterByContentTypeName) 
    #set ($filter = "contentTypeName equals $!filterByContentTypeName")
#else 
    #set ($filter = "")
#end

  • Checks if filterByContentTypeName is set since it’s not mandatory.

  • If set, apply the filter "contentTypeName equals $!filterByContentTypeName".

  • Otherwise, do not apply any filter.

Data Retrieval

#set($collection = $analytics.runReport("count", "identifier, title",
    "", $filter, "count $listBy", 5, 0).getResults())

  • Retrieves data from Analytics using the runReport method


The runReport method expects seven parameters, which are listed in the section below.

Understanding runReport Parameters

The runReport method requires seven parameters, explained in the table:

Parameter

Description

Measure

Aggregation method — e.g., count — to calculate the number of hits for each content item.

Dimension

Fields to retrieve, such as identifier and title.

Time Dimension

Optional time filter for data retrieval (details to be discussed later).

Filters

Apply filters using the syntax: field operator value

In this case, the filter is "contentTypeName equals $!filterByContentTypeName" to filter content by type.


Note: This filter syntax in dotCMS in addition to contentTypeName supports a set of other values like: conHost, contentTypeId, identifier, title, language, persona, etc.


dotCMS also supports certain operator values in this filter, such as notEquals, contains, and notContains.

Order

Field and direction to sort results, using syntax: field direction


Example: count desc. Here, $listBy specifies the direction.

Limit

Number of items to retrieve. In this example, it’s hardcoded to 5, but this value can be dynamic.

Offset

Starting point for retrieving items, useful for pagination. Here, it’s set to 0 (no offset).


The final part of the code makes use of the collected data in templated HTML.

Step 4: Create New Widget Contentlets

Next, we will create a couple of widgets with the following values:

Field

First Widget Contentlet

Second Widget Contentlet

Name:

Less Popular Blog

More Popular Blog

List by:

Less Popular Content

More Popular Content

Filter by:

Blog

Blog


create_widget_contentlets.png

Step 5: Create Page

Go to Pages and click the “Create Page” button to create a new page.

create_page.png

Select “Page” in the dialog:

create_page_dialog.png

And Create a new Page with the title “Analytics Example,” as appears below:

create_new_page.png

Step 6: Add Widgets to Page

Now add the  two newly created contentlets to it.

First, click on the plus button above the container in the Edit Page screen, which will be visible automatically after saving the page.

add_widgets_to_page.png

Select the “Widget” option.

select_widget_option.png

In the “Search Content” dialog, look for the “More Popular Blog” Widget and click on the select button.

add_more_popular_contents.png

Repeat these steps to the “Less Popular Blog,” and you will see the following:

page_completed.png

Note on Style 

To achieve the styling shown in the picture, you can apply the styling to the widget code as follows:

<style>

.table-container {
    max-width: 800px;
    margin: auto;
    overflow-x: auto;
    background: #fff;
    padding: 10px;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

table {
    width: 100%;
    border-collapse: collapse;
    text-align: left;
    margin-top: 10px;
}

thead {
    background-color: #0078D7;
    color: white;
}

th, td {
    padding: 10px 15px;
    border: 1px solid #ddd;
}

tbody tr:nth-child(even) {
    background-color: #f2f2f2;
}

tbody tr:hover {
    background-color: #f1f8ff;
}

th {
    font-weight: bold;
}

td {
    font-size: 14px;
}
</style>

Using the timeDimensions Parameter in the runReport Method

Remember the timeDimensions parameter from the runReport method? We’ll now use it to make our example more engaging, allowing us to apply a date range and time granularity to our example.


Step 1: Add New Fields to the  “Popular Content” Content Type

  • Add a "Time Granularity" Field:

    • Type: Required Radio Field

    • Options:

None|none
Second|second
Minute|minute
Hour|hour
Day|day
Week|week
Month|month
Quarter|quarter
Year|year

Refer to the picture below to see how it looks.

timedimensions_field.png
  • Add a "Date Range" Field:

    • Type: Required Radio Field

    • Options

All Time|All time
Today|today
Yesterday|yesterday
This week|this week
This month|this month
This quarter|this quarter
This year|this year
Last 7 days|last 7 days
Last 30 days|last 30 days
Last week|last week
Last month|last month
Last quarter|last quarter

Refer to the screenshot below to see how it looks.

date_range_field.png

Step 2: Update the Widget Code

Finally, incorporate the required changes in the Widget Code to utilize the new fields effectively.

#if ($filterByContentTypeName) 
    #set ($filter = "contentTypeName equals $!filterByContentTypeName")
#else 
    #set ($filter = "")
#end

#if ($timeGranularity != 'none') 
    #set ($timeGranularityValue = $timeGranularity)
#else 
    #set ($timeGranularityValue = "")
#end

#if ($dateRange != 'All time') 
    #set ($dateRangeValue = $dateRange) 
#else 
    #set ($dateRangeValue = "")
#end

#if ($timeGranularityValue != '' || $dateRangeValue != '') 
    #set ($timeDimensions = "request.createdAt,$!timeGranularityValue:$!dateRangeValue")
#else 
    #set ($timeDimensions = "")
#end

#set($collection = $analytics.runReport("count", "identifier, title",
    $timeDimensions, $filter, "count $listBy", 5, 0).getResults())


<div>
    <h1>$widgetTitle</h1>

    <table class="table-container">
        <thead>
            <tr>
                <th>Identifier</th> 
                <th>Title</th> 
                <th>Count</th>  

                #if ($timeGranularityValue != '') 
                    <th>$timeGranularityValue</th> 
                #end
            </tr>
        </thead>

        <tbody> 
            #foreach($item in $collection)
                <tr>
                    <th>$item.get("request.identifier").get()</th> 
                    <th>$item.get("request.title").get()</th> 
                    <th>$item.get("request.count").get()</th>  

                    #if ($timeGranularityValue != '') 
           <th>$item.get("request.createdAt.$timeGranularityValue").get()</th> 
                    #end
                </tr>
            #end
        </tbody>
    </table>  
</div>

Conclusion 

Content Analytics is a powerful feature that helps you adjust your site's behavior based on user activity. For example, in this blog, we display a list of blog posts ranked by user interest.

You can also use Analytics to monitor your site and see if a specific page or section is getting the attention you expect. With this data, you can decide whether to create an experiment to test and determine the best changes to improve user engagement. 

Full official documentation of this feature will be coming soon — it really is a brand new feature. But we will be publishing another post in the near future with further examples. Stay tuned!