Version 4 supported
This version of Silverstripe CMS is still supported though will not receive any additional features. Go to documentation for the most recent stable version.

Grouping lists of records

The SS_List class is designed to return a flat list of records. These lists can get quite long, and hard to present on a single list. Pagination is one way to solve this problem, by splitting up the list into multiple pages.

In this howto, we present an alternative to pagination: Grouping a list by various criteria, through the GroupedList class. This class is a ListDecorator, which means it wraps around a list, adding new functionality.

It provides a groupBy() method, which takes a field name, and breaks up the managed list into a number of arrays, where each array contains only objects with the same value of that field. Similarly, the GroupedBy() method builds on this and returns the same data in a template-friendly format.

Grouping sets by first letter

This example deals with breaking up a SS_List into sub-headings by the first letter.

Let's say you have a set of Module objects, each representing a Silverstripe CMS module, and you want to output a list of these in alphabetical order, with each letter as a heading; something like the following list:

* B
    * Blog
* C
    * CMS Workflow
    * Custom Translations
* D
    * Database Plumber
    * ...

The first step is to set up the basic data model, along with a method that returns the first letter of the title. This will be used both for grouping and for the title in the template.

namespace App\Model;

use SilverStripe\ORM\DataObject;

class Module extends DataObject
{
    private static $db = [
        'Title' => 'Text',
    ];

    /**
     * Returns the first letter of the module title, used for grouping.
     * @return string
     */
    public function getTitleFirstLetter()
    {
        return $this->Title[0];
    }
}

The next step is to create a method or variable that will contain/return all the objects, sorted by title. For this example this will be a method on a new ModulePage class.

namespace App\PageType;

use App\Model\Module;
use Page;
use SilverStripe\ORM\GroupedList;

class ModulePage extends Page
{
    /**
     * Returns all modules, sorted by their title.
     * @return GroupedList
     */
    public function getGroupedModules()
    {
        return GroupedList::create(Module::get()->sort('Title'));
    }
}

The final step is to render this into a template. The GroupedBy() method breaks up the set into a number of sets, grouped by the field that is passed as the parameter. In this case, the getTitleFirstLetter() method defined earlier is used to break them up.

<%-- Modules list grouped by TitleFirstLetter --%>
<h2>Modules</h2>
<% loop $GroupedModules.GroupedBy(TitleFirstLetter) %>
    <h3>$TitleFirstLetter</h3>
    <ul>
        <% loop $Children %>
            <li>$Title</li>
        <% end_loop %>
    </ul>
<% end_loop %>

Grouping sets by month

Grouping a set by month is a very similar process. The only difference would be to sort the records by month name, and then create a method on the DataObject that returns the month name, and pass that to the GroupedList::GroupedBy() call.

We're reusing our example Module object, but grouping by its built-in Created property instead, which is automatically set when the record is first written to the database. This will have a method which returns the month it was posted in:

namespace App\Model;

use SilverStripe\ORM\DataObject;

class Module extends DataObject
{
    /**
     * Returns the month name this news item was posted in.
     * @return string
     */
    public function getMonthCreated()
    {
        return date('F', strtotime($this->Created));
    }
}

The next step is to create a method that will return all records that exist, sorted by month name from January to December. This can be accomplshed by sorting by the Created field:

namespace App\PageType;

use App\Model\Module;
use Page;
use SilverStripe\ORM\GroupedList;

class ModulePage extends Page
{
    /**
     * Returns all news items, sorted by the month they were posted
     * @return GroupedList
     */
    public function getGroupedModulesByDate()
    {
        return GroupedList::create(Module::get()->sort('Created'));
    }
}

The final step is to render this into the template using the GroupedList::GroupedBy() method.

// Modules list grouped by the Month Posted
<h2>Modules</h2>
<% loop $GroupedModulesByDate.GroupedBy(MonthCreated) %>
    <h3>$MonthCreated</h3>
    <ul>
        <% loop $Children %>
            <li>$Title ($Created.Nice)</li>
        <% end_loop %>
    </ul>
<% end_loop %>

Related