Introduced in version 9, Features are a way to categorize types of functionality provided by Concrete and supported by a theme. In order to describe Features best, it's helpful to look at the problem they're trying to solve.
Block-Level CSS and JavaScript
When rendering a Concrete site, the main view of a particular page is rendered using the content of a page template, alongside whatever header and footer includes that template provides. The theme itself will provide one or more stylesheets, alongside one or more JavaScript files for interactive functionality.
In addition, blocks themselves can also provide JavaScript and CSS. This is useful if a particular block has interactivity; the block doesn't have to rely on the theme explicitly supporting its functionality. The calendar block, for example, might need to have some custom JavaScript for rendering searchable lists or pageable date grids. The Image Gallery block also might need custom CSS for rendering its thumbnails in a pleasing way.
This has been the status quo in Concrete for many years at this point, and it works pretty well. However, there are some problems with it:
- If you add a lot of blocks to a page, and each one of these blocks has a single
view.js
andview.css
template, you'll end up with lots of requests to small JS and CSS files. The on-demand asset caching functionality (which is currently disabled in version 9) has helped mitigate this somewhat in the past, but frequently we recommend disabling it because it can lead to unpredictable results and doesn't always play nicely with CSS and JS provided by third party add-ons. - If a theme developer wants to add custom support for a core block type into their theme, they have to do it in a way that doesn't conflict with the view.js or view.css files that the block will load. There's no way for a theme to disable these view.js or view.css files. Frequently theme developers instead resort to forking blocks and shipping their own copies of blocks – which is needless bloat and code duplication.
- If a block type has code that's related – e..g both the Content block and the Image Gallery block use Magnific Popup for lightboxes – there's no way to change to use a different lightbox library or handle these two use cases without forking both JS files, or overriding their behavior within the theme with some kind of hack.
- Some block types like Calendar are, by their very nature, very opinionated when rendering themselves, shipping with a very specific JS library like Fullcalendar. This ensures that the calendar works – but what if a particular theme or website wants to completely swap out the logic for how the Calendar block renders? There's no easy way to remove Fullcalendar without fully forking the Calendar block type.
- Since the view.css files included in a block type are static, they can't refer to any CSS variables that may or may not be present in the theme. That means their colors or presentation may not match other aspects of the theme.
The Concrete Features feature aims to solve these problems.
What is Concrete Features?
Features are logical groupings of functionality shared across block types and single pages. A block or page controller can specify a certain feature as "required" in order to use that block type. Then, JavaScript and CSS that corresponds to that feature will be loaded whenever those block types or pages are encountered.
Examples of Features registered in the core include
- Basics
- Typography
- Imagery
- Calendar
- Boards
- Video
- Maps
Examples of Concrete block types that use the Imagery feature include:
- Content
- Gallery
- Image Slider
- Hero Image
Examples of Concrete block types that use the Calendar feature include:
- Event List
- Calendar
- Calendar Event
These block types register the Features that they require by implementing the Concrete\Core\Feature\UsesFeatureInterface
class. This class has a single method – getRequiredFeatures
, which returns an array of strings, with each string representing a Feature like Calendar, Basics, or Imagery.
Features are not limited to block types.
Marking features as required by a piece of functionality isn't limited to block type controllers – page controllers can also implement the UsesFeatureInterface
.
\Concrete\Core\Feature\Features Class Registry
You don't have to guess at what Feature strings to return from the getRequiredFeatures
method in a controller. A full list of core features is available as class constants in Concrete\Core\Feature\Features
.
The Features
class is a simple registry class to codify all the core features used by the asset system. Features listed in here are provided in some capacity by the core.
Base/Fallback Assets
By itself, the Features system described above wouldn't do anything; it simply marks certain block types as requiring some class of assets. However, when paired with feature assets that ship with Concrete, we can get a sense of how Features help deliver JavaScript and CSS to pages and themes that require them.
Base Feature assets are JavaScript and CSS files that correspond to a particular feature. These are built and shipped by the core and found in the concrete/js/features
and concrete/css/features
directories. These are registered within the assets system. Whenever a block type that uses a particular feature is used, a request on that page is made for the feature. For example, using an Image Slider block on a page will cause the assets system to include the feature/imagery/frontend
asset group. This group consists of concrete/js/features/imagery/frontend.js
and concrete/css/features/imagery/frontend.css
, and these assets will be included in the header and footer.
Theme Features
The final piece of the Features Puzzle are "Theme Features." These, when combined with Fallback Feature Assets and the Features system provide the answer to the problems described above. Themes can implement the getThemeSupportedFeatures
method in their PageTheme
classes; this method returns an array of strings just like getRequiredFeatures
.
In our example above with the Image Slider, the feature/imagery/frontend
JavaScript and CSS files will be included on the page – unless the theme that's rendering them has included Features::IMAGERY in their getThemeSupportedFeatures
array. Including Features::IMAGERY in this array is a sign to Concrete that the theme itself already supports the Imagery feature, and there's no need to use the fallback assets. Themes should do this if they're including their own copies – modified or otherwise – of the JavaScript and CSS required to fulfill the requirements of a particular feature.
That's why the feature assets described above are referred to as "Fallback" assets; they are included in order to satisfy a base level of functionality. With fallback assets, block developers – including the core team – can know that a block type will always work to a certain degree. However, a theme that wants to employ a richer or more customized experience displaying or interacting with a particular block type view can ship its own implementation of a particular Feature.
Bedrock Theme Trait Features
When using the BedrockThemeTrait
class in your PageTheme
, you'll automatically opt in to a couple basic features:
- Basics
- Typography
That's because the trait implements the method:
public function getThemeSupportedFeatures()
{
return [
Features::BASICS,
Features::TYPOGRAPHY
];
}
As a theme developer, you're free to override this method or extend it with whatever features your theme implements. The Bedrock theme trait marks these features as supported because, by opting into Bedrock, it's clear that your theme supports basic features of Concrete like nice typography (buttons, display headings) and basics (The file block, the image block, the feature link block, etc...)
Let's go a step further and implement the Imagery feature in our theme.