DataProvider for SmartContent
=============================
DataProviders are used to load data for SmartContent. It returns data filtered
by a configuration array. This array can be configured with an overlay in the
backend form.
This configuration array includes following values:
.. list-table::
:header-rows: 1
* - Name
- Description
* - dataSource
- Additional constraint - like page-"folder".
* - tags
- Multiple selection of tags, which a item should have.
* - tagOperator
- The item has any or all of the selected tags.
* - categories
- Multiple selection of categories, which a item should have.
* - categoryOperator
- The item has any or all of the selected categories.
Tags (websiteTags) and Categories (websiteCategories) can also be "injected" by
GET parameters from the website. This can be handled separately from the
admin-selected. Also different operators (websiteTagsOperator and
websiteCategoryOperator) are available.
Additional features, which can be provided with a DataProvider:
.. list-table::
:header-rows: 1
* - Name
- Description
* - presentAs
- Value can be used in the website for display options - like one or two
column - these values can be freely configured by developers.
* - page & pageSize
- Pagination of items.
* - limit
- Maximum items for (if pagination is active) over all pages or overall.
How to create a custom DataProvider?
------------------------------------
To create a custom data provider you simply have to create a service which
implements the Interface `DataProviderInterface`. This Interface provides
functions to resolve the configured filters for the backend API with
standardized objects and for the website with array and entity access.
Additionally the DataProvider returns a configuration object to enable or
disable features.
There exists an abstraction layer for ORM DataProviders. This layer provides the
implementation of basic DataProvider functionality and Database query.
If you want to create a DataProvider for the ExampleEntity you have todo
following steps.
1. Repository
~~~~~~~~~~~~~
The repository has to implement the DataProviderRepositoryInterface and provide
the findByFilters function. If the default implementation is good enough, you
can include the trait `DataProviderRepositoryTrait`, which, needs the functions
`createQueryBuilder` (is default in repositories) and `appendJoins` where you
are able to configure eager loading for the entity.
The rest of the functionality and Query generation is done in the Trait.
.. code-block:: php
addSelect('type')->leftJoin($alias . '.type', 'type');
}
}
.. note::
Be sure that the returned entities has valid serialization configuration for
JMS\Serializer.
There are following hooks to influence the query generation. These are functions
which are optional to override in the repository.
.. list-table::
:header-rows: 1
* - Name
- Description
* - append(QueryBuilder $queryBuilder, $alias, $locale, $options = [])
- Additional `select`, `where` or `joins` can be added to the query to
match given options. The options can be generated by the data-provider
and can contain for example additional filter parameter.
* - appendTagsRelation(QueryBuilder $queryBuilder, $alias)
- If your entity is not directly connected to the tags (entity.tags) you
can append here all needed joins and return the path to the tag
relation.
* - appendCategoriesRelation(QueryBuilder $queryBuilder, $alias)
- Same as tags.
* - appendDatasource($datasource, $includeSubFolders, QueryBuilder
$queryBuilder, $alias)
- If your dataprovider can handle datasources you can add the
functionality to filter by the datasource here.
2. DataItem
~~~~~~~~~~~
The DataItem will be used in the backend to display the filtered items. This
class implements the Interface `ItemInterface`.
.. code-block:: php
entity = $entity;
}
/**
* {@inheritdoc}
*
* @VirtualProperty
*/
public function getId()
{
return $this->entity->getId();
}
/**
* {@inheritdoc}
*
* @VirtualProperty
*/
public function getTitle()
{
return $this->entity->getTitle();
}
/**
* {@inheritdoc}
*
* @VirtualProperty
*/
public function getImage()
{
return $this->entity->getImage();
}
/**
* {@inheritdoc}
*/
public function getResource()
{
return $this->entity;
}
}
.. note::
If you return an image within the `getImage` function it will be displayed
in the admin ui. You should be sure that the image is not bigger that 50x50.
3. DataProvider
~~~~~~~~~~~~~~~
Also the DataProvider is mostly abstracted by the SmartContent Component. The
optimize in the configuration you can disable or enable the form-elements to
avoid filtering for that values.
.. code-block:: php
requestStack = $requestStack;
$this->configuration = self::createConfigurationBuilder()
->enableTags()
->enableLimit()
->enablePagination()
->enablePresentAs()
->setDeepLink('examples/example/{webspace}/{locale}/{id}')
->getConfiguration();
}
/**
* Decorates result as data item.
*
* @param array $data
*
* @return ItemInterface[]
*/
protected function decorateDataItems(array $data)
{
return array_map(
function ($item) {
return new ExampleDataItem($item);
},
$data
);
}
/**
* Returns additional options for query creation.
*
* @param PropertyParameter[] $propertyParameter
* @param array $options
*
* @return array
*/
protected function getOptions(array $propertyParameter, array $options = []) {
$request = $this->requestStack->getCurrentRequest();
$result = [
'type' => $request->get('type'),
];
return array_filter($result);
}
}
.. note::
The deep-link will be used to generate a the link to the Sulu-Admin form of
a single item, when the user click on it.
4. Service Definition
~~~~~~~~~~~~~~~~~~~~~
Define a service with your Repository and DataProvider and add the tag
`sulu.smart_content.data_provider` with a alias to your DataProvider service
definition.
.. code-block:: xml
%sulu_example.example.entity%
Afterwards you can use your new DataProvider within a normal SmartContent
property.
How to create a custom Datasource component?
--------------------------------------------
A Datasource component is a simple aura-component which returns some data. These
data can be used in the `DataProviderRepository::appendDatasource` method.
For example returns the Content-DataProvider the UUID of the page which should
be used as the parent of result set.
The following example is a simple (and not complete) example of a datasource
component. If you need a full example please take a look at the components
`media-datasource@sulumedia` or `content-datasource@sulucontent`.
.. code-block:: javascript
define(function() {
'use strict';
var defaults = {
options: {
url: null,
resultKey: null,
selected: null,
selectCallback: function(item) {
}
},
templates: {
skeleton: '' // TODO html skeleton to render component
}
},
/**
* namespace for events
* @type {string}
*/
eventNamespace = 'smart-content.datasource.';
return {
defaults: defaults,
events: {
names: {
setSelected: {
postFix: 'set-selected',
type: 'on'
}
},
namespace: eventNamespace
},
/**
* Initialize component
*/
initialize: function() {
// merge options with defaults
this.options = this.sandbox.util.extend(true, {}, defaults, this.options);
// current selected datasource
this.selected = this.options.selected;
// render skeleton and start subcomponents
this.render();
},
/**
* Bind events to call select callback
*/
bindCustomEvents: function() {
// setter for selected
this.events.setSelected(this.setSelected.bind(this));
},
/**
* Set new selected and update UI.
*
* @param {String} selected can also be null.
*/
setSelected: function(selected) {
this.selected = selected;
// TODO update component with new selected datasource
},
/**
* These function should be called to propagate the result to smart-content component.
*
* @param {String} selected can also be null.
*/
emitSelected: function(item) {
this.selected = item.id; // identifier of item
this.options.selectCallback(
this.selected, // will be saved and used to generate the query
item.path // will be displayed on the first slide
);
},
/**
* Render container for column-navigation
*/
render: function() {
this.$container = this.sandbox.dom.createElement(
this.templates.skeleton()
);
this.html(this.$container);
}
};
});
To activate these datasource-component it has to be enabled in the DataProvider.
.. code-block:: php
configuration = self::createConfigurationBuilder()
->enableTags()
->enableLimit()
->enablePagination()
->enablePresentAs()
->enableDatasource(
'example@suludoc',
[
'url' => '/admin/api/example',
'resultKey' => 'examples',
]
)
->getConfiguration();
}
The component name and options will be used to initialize the component.
Therefor you can use the url wil `this.options.url`.