CMS - SmartModules
Creating your own SmartModule
## Introduction
---
**ABC SmartModules** are a CMS module type that allows you to extend the CMS interface with your own views. In contrast to
the default 'content modules', this type of module invokes a Controller Action, that lets you use the full application
stack and render your own template. In addition to that, you are able to load custom JavaScript and CSS for your
SmartModule.
In this demonstration we will demonstrate a SmartModule that can change the appearance of the blog demo.
Please read our [SmartModules](https://docs.angrybytes.com/p/abc-manager/6.0/docs/cms/smart_modules/index.html)
documentation chapter first.
This demonstration assumes you have already read the documentation about creating an ABC SmartModule and will not explain the details in depth.
Note - all demonstrations occur in realtime! Open this project in your IDE to view and/or
modify the actual sourcecode used in the demonstrations.
### The Blog Configurator Dashboard SmartModule
This module demonstrates how to use a SmartModule to configure the look & feel of an application; the blog page in the demo app.
It allows users to change the theme and rendering options for the blog section.
The end result:
![SmartModule](/bundles/demopublicationbundle/img/demo/blogconfig.png)
We will now describe how the SmartModule has been created.
#### The SmartModule Controller
The controller style class will render the SmartModule view, include addition CSS an JavaScript assets for the view
and maps module "types" (as used in the `menu.yml`) to controller actions (methods).
The basic class structure looks as follows:
```php
namespace Demo\ContentBundle\BackendInterface\SmartModule;
use Abc\RestBundle\Http\Helpers\Response\ErrorBuildersTrait;
use Abc\RestBundle\Http\Helpers\Response\JsonTrait;
use Abc\RestBundle\Http\Helpers\Response\FractalTrait;
use Demo\ContentBundle\Traits\Settings as SettingsTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use League\Fractal\Resource\Item;
use League\Fractal\Serializer\JsonApiSerializer;
/**
* Configurator Module For Demo Blogs
*/
final class BlogConfigurator extends \Abc\BackendBundle\SmartModule
{
// Traits
use ErrorBuildersTrait, FractalTrait, JsonTrait, SettingsTrait;
/**
* {@inheritdoc}
*/
public function getSupportedTypes(): array
{
return ['demo-blog-configurator'];
}
/**
* {@inheritdoc}
*/
public function getTypeToActionMapping(): array
{
return [
'demo-blog-configurator' => 'configureBlog'
];
}
public function getView(): string
{
return 'blog_configurator';
}
public function configureBlogAction(Request $request): Response
{
$id = 'blog_settings';
$itemsPerPage = [2, 5, 10, 25];
$success = true;
// Persist the settings if the request is a POST.
if ($request->getMethod() === Request::METHOD_POST) {
$settings = [
'items' => $request->request->get('items'),
'theme' => $request->request->get('theme'),
'view' => $request->request->get('view')
];
$success = $this->persistSettings((object) $settings);
}
// Fetch settings from the database.
$settings = $this->getSettings();
$data = compact('id', 'itemsPerPage', 'settings', 'success');
// Create a resource for the Fractal manager.
$resource = new Item($data, function ($data) {
return $data;
}, 'settings');
// Return the response in JSON.
return $this->json(
$this->getFractalManager()->createData($resource)->toArray()
);
}
protected function getFractalSerializer(): JsonApiSerializer
{
return new JsonApiSerializer;
}
}
```
You can see in the `getTypeToActionMapping()` that the value in the array is the action method. This method can be
called as a get request or as a post, depending on what method you chose in your api call.
Now, lets register the module in the service container, so ABC Manager can load the **tagged service**:
```yaml
# File: ./src/Demo/ContentBundle/Resources/config/services/cms-modules.yml
# Blog Configurator smartmodule
demo.cms.smart_module.blog_configurator:
class: Demo\ContentBundle\BackendInterface\SmartModule\BlogConfigurator
parent: demo.backend.smart_module
# Tag it as a smart module
tags:
- name: demo.backend.smart_module
```
ABC Manager is now able to find and load the SmartModule.
The SmartModule requires to be included in the CMS menu configuration file `menu.yml` for the demo application
so it becomes accessible in the CMS:
```yaml
# File: ./applications/demo/application/Resources/config/menu.yml
# CMS Menu configuration for the "demo" application
menu:
# Label for the application used in the CMS interface.
label: 'Demo App'
# Module groups collection
modulegroups:
demo:
label: 'Demo'
modules:
blog_config:
label: 'Blog configurator'
# Set type to smart module
type: smart_module
# Configure the Smart Module by its tag
smart_module: demo-blog-configurator
```
At this point, we have successfully created the SmartModule and are ready to fill in the details.
#### The SmartModule Logic
The SmartModule loads JS/CSS assets to be included in the CMS:
```php
// File "src/Demo/ContentBundle/Bundle.php"
/**
* {@inheritdoc}
*/
public function getCmsJavaScript(): array
{
return [
'/bundles/democontentbundle/dist/bundle.js'
];
}
```
In this example we create the smart module in Vue JS. For more
information how to setup the frontend server with Webpack see the
documentation.
First we take a look at our `main.js`. As can be seen in the code, we import `Abc` (window.Abc). The `Abc` variable
exports libraries & functions which you can reuse in your smart module, but also contains all registered fields, smart
modules & widgets. As you can see we merge our smart modules (`smartModules/index.js`) with the `Abc.smartModules`
```javascript
// File: src/Demo/ContentBundle/Resources/front/src/main.js
import Abc from 'abc-manager'
import fields from './fields'
import overviewFields from './overviewFields'
import widgets from './widgets'
import smartModules from './smartModules'
// Add our fields to the ABC collections.
Object.assign(Abc.fields, fields)
Object.assign(Abc.overviewFields, overviewFields)
Object.assign(Abc.widgets, widgets)
Object.assign(Abc.smartModules, smartModules)
```
We then import the BlogConfigurator in the index.js. The key in the collection must match the getView property in the
smart module.
```javascript
// File: src/Demo/ContentBundle/Resources/front/src/smartModules/index.js
import BlogConfigurator from './BlogConfigurator'
// The keys in this collection match the `getView` property
// of the smart module in PHP.
export default {
blog_configurator: BlogConfigurator
}
```
After this the BlogConfigurator is registered. But we only need to create the BlogConfigurtor view.
```javascript
// File: src/Demo/ContentBundle/Resources/front/src/smartModules/BlogConfigurator.vue
```
The basic SmartModule is now **finished**!