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:
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:
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.
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.
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.
Post your comment
Comments
Divi is this idea taken to the next level.
Posted by Dave, 06/10/2016 2:36pm (8 years ago)
Posted by Will, 06/10/2016 2:33pm (8 years ago)
---
@Giancarlo It would work for both.
Anna could use the 'link existing' function provided my the gridfield.
Bob could also use strips. He could approach it 2 ways:
1. He could build out a strip with a boolean (GalleryLayout => boolean) value that defines the layout. He could then apply a class to the strip to change the style to present the photos in either a gallery or a slider format and inject required javascript conditionally. Depending on the design of the two elements, he would likely need different markup.
2. Bob could wrap his template in a conditional if block and serve up a different template based on the boolean value.
3. He could create 2 seperate strips (one with a gallery and one with the slider) and link them up to a global photos strip (or even just a data object that manages many images).
Note that this could probably be done with the blocks module as well.
---
@Vitaliy It has not been open sourced. I recommend the blocks module for a solid open platform. This tutorial was meant to just demonstrate the concept and how to roll your own.
---
@Paul Clarke Strips made a lot of sense to us as we tend to design in this manner and was easy to convey to clients and other non dev or design people in our agency.
Posted by Nick Spiel, 14/09/2016 1:17pm (8 years ago)
Out of curiosity and as a contributor to the Blocks module; what made you still decide to implement similar/custom code yourself (any comments/improvements on the existing Blocks module)?
I'd like to plug our Block-Enhancements module as a nice Blocks companion: https://github.com/restruct/silverstripe-block_enhancements
Posted by Michael, 07/09/2016 3:14pm (8 years ago)
1. Anna has her site divided in parts, each part with the same content and structure. She wants to create a promo hero once in a single place, so that it gets updated in sync automatically in every part of her website.
2. Bob has his site divided in parts, each part with the same structure, but different content. For example, On a page he needs a photo gallery, on another page he needs a photo slider, on a third page he needs both. Each page has unique content, he wants to be able to pick parts from a library and configure them individually.
Do you think these Anna and Bob can do that with Blocks, Strips or both?
Posted by Giancarlo, 06/09/2016 2:19am (8 years ago)
Can you get link ?
Posted by Vitaliy, 05/09/2016 7:17pm (8 years ago)
Posted by Paul Clarke, 05/09/2016 5:19pm (8 years ago)
No one has commented on this page yet.
RSS feed for comments on this page | RSS feed for all comments