Do you ever find yourself writing increasingly complex code, just to show interesting content on your home page? I'm talking about the kind of code that's hard to test and even harder to figure out?
class HomePageController extends Controller { public function getProducts() { $stores = StorePage::get(); $departments = new ArrayList(); foreach ($stores as $store) { $departments->merge( $store->Departments()->column("ID") ); } $departments = Department::get()->byIDs($departments); $products = new ArrayList(); foreach ($departments as $department) { $products->merge( $department->Products()->column("ID") ); } return Product::get()->byIDs($products) ->sort("Price") ->limit(5); } }
Unless the pages you're looking to display are children of the page on which you're trying to display them, things quickly fall apart. The NZ Transport Agency recently needed to do something similar to this for their website, and a team at SilverStripe decided to tackle this problem, and produce a reusable solution.
You can find the code for this module on Github.
Getting Things Ready
The team developed a module you can install via:
$ composer require silverstripe/superglue
The first step to using it is to define an interface for things we want to glue to landing pages:
use SilverStripe\SuperGlue\Connector; class ProductsConnector implements Connector { public function getTitle() { return "Home Page → Product Pages"; } public function getTemplate(SiteTree $page) { return "HomePageProductPages"; } public function getDataList(SiteTree $page) { return ProductPage::get() ->filter("Department.ParentID", $page->ID); } }
You can define any number of these connectors, defining relationships between different kinds of landing pages and the sub-pages they display.
For each, you should enable extensions on the pages. Pages that display data get one extension, while pages that are displayed get another:
HomePage: extensions: - SilverStripe\SuperGlue\PageExtension HomePage_Controller: extensions: - SilverStripe\SuperGlue\PageControllerExtension ProductPage: extensions: - SilverStripe\SuperGlue\SubPageExtension
Finally, you need to define the template for the included pages and add a special template variable to the home page template:
In HomePage.ss
<% if $SuperGlueConnector %> $SuperGlueView <% end_if %>
In HomePageProductPages.ss
<% with $Parent %> <% if $SuperGlueViewSubPages %> <% loop $SuperGlueViewSubPages.Limit(5) %> <div class="product">$Title → $Price</div> <% end_loop %> <% end_if %> <% end_with %>
These files create the framework for displaying sub-pages on your special landing page types. They're only half the equation though...
Switching Connectors
Once you add the PageExtension and SubpageExtension classes to the pages you're trying to connect, you will see a few new fields:
Those "connected items" fields were added by the Superglue module. The new Connector class we created should be visible in the dropdown, so we can select it.
If you don't see your Connector class, you may need to run dev/build?flush=1.
Once we select it, and save the page, a new CMS tab will appear:
With this list, we can decide the order of pages we want to display. As new pages are created, they are added to the top of the "Normal Pages" list. We can drag and reorder them. We can also pin pages to the top of our page. Pinned items are displayed first in the template we created.
We'll only be able to pin sub-pages until we reach the limit in settings. After that, the "pin" buttons will disappear.
We can also customise the columns displayed in the CMS grid field, by adding a method to the Connector:
public function getGridFieldDisplayFields(SiteTree $page) { return [ "Title" => "Product name", "Price" => "Product price", ]; }
Loading More
The Superglue module was designed to work well with ajax pagination, so you can create infinitely-scrolling landing pages easily. We can use a special link, in our controllers:
<% with $Parent %> <button class="load-more" data-page-limit="$SuperGluePageLimit" data-first-page-limit="$SuperGlueFirstPageLimit" data-load-more-link="$LoadMoreLink" > Load more </button> <% end_with %>
Ajax requests to this URL will return a JSON response resembling:
{ "total": 123, "limit": 9, "start": 16, "next": "special-landing-page\/LoadMore?start=16", "items: [...] }
We might also want to alter the sub-page data, before it gets to the browser. We can do this by adding another method to the Connector:
public function getPageArray(SiteTree $page) { $date = DBField::create_field( "SS_Datetime", $page->Created ); return array( "link" => $page->Link(), "title" => $page->Title, "price" => $page->Price, "created" => date->Format("j M Y"), ); }
The templates we created will always have access to the DataList returned by getDataList. getPageArray is just for the ajax load more endpoint.
Wrapping up
This module is useful in terms of the amount of code it saves us from creating, testing and maintaining. It goes really well with a library like Isotope, or a Masonry layout.
We'd like to thank NZ Transport Agency for letting us be creative and contribute to open source.
If you'd like to know more about this module, or want to suggest improvements, let us know in the comments below!
Header photo by Chris Isherwood
Post your comment
Comments
No one has commented on this page yet.
RSS feed for comments on this page | RSS feed for all comments