Skip to main content

This site requires you to update your browser. Your browsing experience maybe affected by not having the most up to date version.

 

SilverStripe Strips

In this guest post, Nick Spiel of Digital360 shares how they created a method of building pages with modular 'strips'.

Read post

Introducing SilverStripe Strips: a fast and scalable method to building modular webpages

Popular front-end frameworks like React and Vue have placed more emphasis the value of breaking down projects into components for reuse across the build. This is in line with a general trend that favours modular components or ‘patterns’ in web development.

Recently, my team and I have developed a new approach to building websites in SilverStripe. Instead of building out entire page templates, we have created a method of building pages with modular 'strips'.

Others in the SilverStripe community have been moving in a similar direction. Shae Dawson’s SilverStripe blocks module is one great, mature example. In this article, I’ll share how my team and I came to develop a similar solution.

There are a number of benefits to this strip process. These include:

  • Fast page customisation: Strips can be added, removed, updated and even re-ordered on pages independently of other pages. Structural changes to a page no longer have to go through development and can be completed by any CMS user (and this can be restricted to admins only).
  • Less repetition (DRY): Each strip is self-contained, so there is no need to copy across code to multiple layouts.
  • Time savings: Strips allow teams to allocate the task of ‘building’ pages to non-technical team members. This will give developers more time to focus on technical tasks.
  • Modularity: Each 'strip' will have its own [.ss, .php, .css] and perhaps even a [.js] file. These files can be copied into any project to avoid re-building common patterns.
  • Single source: Layouts, models and controllers are independent and have a single source for making changes. This means that managing elements is far simpler.
  • Reusable: Strips can be included on multiple pages across the site, making the administration of common content simple.

This process speeds up development time and makes our lives easy when publishing content or developing updates.

The traditional approach to building Layouts

When we started building sites in SilverStripe we adopted a very traditional approach: building out page templates in the [templates/Layout directory]. These pages were made up of a combination of includes and inline markup.

Here is an example of a typical page style in one of our builds:

Example: templates/Layout/ServicePage.ss
<% include HeroBanner %>
<section class="main-content">
...
{$Content}
...
</section>
<% include CallToAction %>
<% include EnquiryForm %>

Your process and templates may look similar to this.

While this method is tried and true, it can be time-consuming and tedious. You end up building out as many layouts as there are unique page styles for the project.

How can we improve on this process?

As we developed our client’s websites, we found many projects used similar elements across separate pages. These elements often appeared in a different order, too.

Consider the following scenario:

wireframe

These two pages are almost identical. The only difference is that the ‘content block’ and ‘call to action’ appear at different points on the page. Traditionally, we would have needed to create an entirely new page template to facilitate this change (and come up with a new name, too).

This is where the strip method becomes so valuable.

When building using strips, you craft the strip directly inside the CMS, duplicate them, reuse them, and reorder them. You can even nest them within each other.

Here is an example of the strips at work on a current live site:

strips gridfield

The strips are created and managed through a GridField which provides flexibility and some valuable functionality.

We have included the GridFieldExtensions module to add additional functions to the GridField. This supports the strip system in two ways: 

  • Managing multiple classes: The MultiClass component allows us to add different strip types to the page. This way each strip can have its own unique set of fields and templates.
  • Drag and drop reordering: The OrderableRows component provides the ability to drag and drop reorder the strips. 

Implementation

Page object: site/code/Page.php

First we setup a many_many relationship from Page to our Strip object. It is important that we make this relationship a many_many so that global or repeated strips can be included on multiple pages. We also add an extra field to the many_many relationship so we can control the order of the strips in the context of the relationship.

private static $many_many = [
'Strips' => 'Strip'
];

// Add a SortOrder field to the relationship to allow for reordering
private static $many_many_extraFields = [
'Strips' => [
'SortOrder' => 'Int'
]
];

Next, we set up the GridField. The GridField uses the multiclass editor plugin from the GridFieldExtensions.

// Set the columns to be displayed in the gridfield
$dataColumns = new GridFieldDataColumns();
$dataColumns->setDisplayFields(
    [
        'Title'         => 'Title',
        'singular_name' => 'Type'
    ]
);

// Get available strips from 'strips' folder as an array
$multiClassConfig = new GridFieldAddNewMultiClass();
$multiClassConfig->setClasses($this->AvailableStrips());

// Set up autocompleter to add existing strips
$autocompleter = new GridFieldAddExistingAutocompleter('toolbar-header-right', ['Title']);
$autocompleter->setResultsFormat('$Title ($ClassName)');

// Create the gridfield
$stripsGridField = GridField::create('Strips', 'Strips', $this->Strips(),
    GridFieldConfig::create()->addComponents(
        new GridFieldToolbarHeader(),
        new GridFieldDetailForm(),
        new GridFieldEditButton(),
        new GridFieldDeleteAction('unlinkrelation'),
        new GridFieldDeleteAction(),
        new GridFieldTitleHeader(),
        new GridFieldOrderableRows('SortOrder'),
        $autocompleter,
        $multiClassConfig,
        $dataColumns
    )
);

$fields->addFieldsToTab(
    'Root.Strips',
    [
        $stripsGridField
    ]
);

We then create a method for automatically populating the dropdown for the MultiClass editor. All php files under /site/code/strips will be returned as an array.

private function AvailableStrips()
{
// TODO Is there a better method for getting this folder?
    $stripFolder = BASE_PATH . '/site/code/strips';
    $strips      = [];

    foreach (new DirectoryIterator($stripFolder) as $file) {
        if ($file->isFile()) {
            $fileName  = $file->getFilename();
            $fileParts = pathinfo($fileName);
            // Only add PHP files
            if ($fileParts['extension'] == 'php') {
                array_push($strips, $fileParts['filename']);
            }
        }
    }
    // Sort strips alphabetically
    asort($strips);

    return $strips;
}
Strip object: site/code/Strip.php

Next, we set up a base class for our strips. I have commented inline with the purpose of each section.

code5

site/templates/Layout/Page.ss

Then we tie this all together with the following loop in your default (and only) page template.

1 <% loop $Strips.Sort(SortOrder) %> 
2 {$Me}
3 <% end_loop %>

This loop runs over all of the strips on the current page and through the use of {$Me} , then renders them using the forTemplate method we set on the base Strip class.

Once you have completed those steps, you can start extending this class with your custom

A Practical Example:

CallToActionStrip object: site/code/CallToActionStrip.php

Let’s take the example I used earlier with the ‘call to action’ strip and build it out using this process. The strip has the following elements:

  • An small image
  • A line of text
  • A button

So nothing special here. Just be sure to extend Strip.

code7 

CallToActionStrip template: site/templates/Includes/CallToActionStrip.ss
<section class="call-to-action-strip"> 
{$Image}
<p class="text">{$Text}</p>
<a href="{$PageLink.Link}" class="button">{$ButtonText}</a>
</section>

The implementation is complete! Finally, you can rid yourself of Page layouts almost entirely.

Final words

We have tried this method on our own website and three of our client builds. In short: it has revolutionised our approach to development. Our team can't see ourselves going back to the traditional approach. In fact, the content publishers and account managers are now complaining that they don’t have this flexibility on all of our client builds.

Some additional observations:

  • Code is more transportable between projects.
  • A/B testing was difficult to manage. We found that we had to wrap the entire page in a conditional block or build out an entirely new B version.
  • Client requests are no longer a hassle. In the past, if the client wanted to reorder the content we would need to go through the development team to make these changes.
  • Our CMS is now less cluttered. Before using strips, if we needed a similar page style built out we would often need to create a similar page style and change the portions that required it.
  • This is really just the bare essentials to working in a componentised manner in SilverStripe. If you are looking for a ready-to-rock repo I highly recommend the SilverStripe Blocks module.

For more information, please leave a comment or email me at nick.spiel@digital360.com.au or contact Digital360.

About the author
Nick Spiel

Nick Spiel is a Senior Front End Developer at Digital360,  a digital agency in Melbourne, Australia.