How to Create Custom DataGrid using Akeneo
In Akeneo, If you want to display a list of item in a grid system with CRUD functionalities. You can use Akeneo Datagrid. the benefits of Akeneo grid component are filtering, sorting, pagination, and search item. Using Akeneo you can define the form to edit or display an item easily. Let’s start with an example with multi credentials for a connector.
First, you need to create an entity for data grid items. As Akeneo relies heavily on standard tools like Doctrine,
# /src/Webkul/CustomBundle/Entity/CredentialsConfig.php
<?php
namespace Webkul\CustomBundle\Entity;
/**
* CredentialsConfig
*/
class CredentialsConfig
{
/**
* @var integer
*/
private $id;
/**
* @var string
*/
private $url;
/**
* @var string
*/
private $apiUser;
/**
* @var string
*/
private $apiKey;
/**
* @var boolean
*/
private $active = 0;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set url
*
* @param string $url
*
* @return CredentialsConfig
*/
public function setUrl($url)
{
$this->url = $url;
return $this;
}
/**
* Get url
*
* @return string
*/
public function getUrl()
{
return $this->url;
}
/**
* Set apiUser
*
* @param string $apiUser
*
* @return CredentialsConfig
*/
public function setApiUser($apiUser)
{
$this->apiUser = $apiUser;
return $this;
}
/**
* Get apiUser
*
* @return string
*/
public function getApiUser()
{
return $this->apiUser;
}
/**
* Set apiKey
*
* @param string $apiKey
*
* @return CredentialsConfig
*/
public function setApiKey($apiKey)
{
$this->apiKey = $apiKey;
return $this;
}
/**
* Get apiKey
*
* @return string
*/
public function getApiKey()
{
return $this->apiKey;
}
/**
* Set active
*
* @param boolean $active
*
* @return CredentialsConfig
*/
public function setActive($active)
{
$this->active = $active;
return $this;
}
/**
* Get active
*
* @return boolean
*/
public function getActive()
{
return $this->active;
}
/**
* Change the active value vice-versa
*/
public function activation()
{
$this->active = !$this->active;
return $this;
}
/**
* @var string
*/
private $resources;
/**
* Set resources
*
* @param string $resources
*
* @return CredentialsConfig
*/
public function setResources($resources)
{
$this->resources = $resources;
return $this;
}
/**
* Get resources
*
* @return string
*/
public function getResources()
{
return $this->resources;
}
}
Now create a Datagrid.
#src/Webkul/CustomBundle/Resources/config/datagrid
datagrid:
webkul-custom-credentials-mapping-grid:
extended_entity_name: Webkul\CustomBundle\Entity\CredentialsConfig
options:
entityHint: CredentialsConfig
manageFilters: false
source:
acl_resource: webkul_custom_connector_configuration
type: custom_datasource_credentials
entity: 'Webkul\CustomBundle\Entity\CredentialsConfig'
query:
select:
- wem.id
- wem.url
- wem.apiUser
- wem.apiKey
- wem.active
from:
- { table: 'Webkul\CustomBundle\Entity\CredentialsConfig', alias: wem }
columns:
url:
label: webkul_custom.credentials.grid.export_mappings.columns.url
frontend_type: label
apiUser:
label: webkul_custom.credentials.grid.export_mappings.columns.apiUser
active:
label: Activated
type: twig
template: PimDataGridBundle:Property:activated.html.twig
frontend_type: html
properties:
id: ~
edit_link:
type: url
route: webkul_custom_credentials_edit
params:
- id
toggle_link:
type: url
route: webkul_custom_credentials_change_status
params:
- id
delete_link:
type: url
route: webkul_custom_credentials_delete
params:
- id
sorters:
columns:
url:
data_name: wem.url
apiUser:
data_name: wem.apiUser
active:
data_name: wem.active
default:
url: '%oro_datagrid.extension.orm_sorter.class%::DIRECTION_ASC'
filters:
columns:
url:
type: search
data_name: wem.url
active:
type: boolean
label: Activated
data_name: wem.active
actions:
toggle:
launcherOptions:
className: AknIconButton AknIconButton--small AknIconButton--switch
type: navigate
label: Change status
link: toggle_link
acl_resource: webkul_custom_connector_configuration
edit:
launcherOptions:
className: AknIconButton AknIconButton--small AknIconButton--edit
type: navigate
label: Edit
link: edit_link
acl_resource: webkul_custom_connector_configuration
rowAction: true
delete:
launcherOptions:
className: AknIconButton AknIconButton--small AknIconButton--trash
type: delete
label: Delete
link: delete_link
acl_resource: webkul_custom_connector_configuration
you need to define the data source service for datagrid.
#src/Webkul/customBundle/Resources/config
services:
webkul_custom_datagrid.datasource.credentails:
class: Webkul\customBundle\Datasource\CredentailsDatasource
arguments:
- '@webkul_custom.repository.credentials'
- '@pim_datagrid.datasource.result_record.hydrator.default'
tags:
- { name: oro_datagrid.datasource, type: custom_datasource_credentials }
Define the Datasource class which is used in data source service.
<?php
namespace Webkul\CustomBundle\Datasource;
use Pim\Bundle\DataGridBundle\Datasource\Datasource;
use Webkul\CustomBundle\Repository\CustomCredentailsRepository;
use Pim\Bundle\DataGridBundle\Datasource\ResultRecord\HydratorInterface;
class CredentailsDatasource extends Datasource
{
/** @var CustomCredentailsRepository */
protected $repository;
/** @var CustomObjectIdHydrator */
protected $hydrator;
/** @var array */
protected $parameters = [];
/**
* @param CustomCredentailsRepository $om
* @param HydratorInterface $hydrator
*/
public function __construct(CustomCredentailsRepository $repository, HydratorInterface $hydrator)
{
$this->repository = $repository;
$this->hydrator = $hydrator;
}
/**
* @param string $method the query builder creation method
* @param array $config the query builder creation config
*
* @return Datasource
*/
protected function initializeQueryBuilder($method, array $config = [])
{
$this->qb = $this->repository->$method('wem');
return $this;
}
/**
* {@inheritdoc}
*/
public function getResults()
{
return $this->hydrator->hydrate($this->qb);
}
}
Define the Access control list ( acl.yml )
#src/Webkul/CustomBundle/Resource/config/acl.yml
webkul_custom_connector_configuration:
type: action
label: Custom Connector
group_name: ~
Define the following routes as used in datagrid for actions edit, change status, delete.
- webkul_custom_credentials_edit
- webkul_custom_credentials_change_status
- webkul_custom_credentials_delete
#src/Webkul/customBundle/Resources/config/routing.yml
webkul_custom_credentials_edit:
path: /custom-configuration/rest/edit/{id}
requirements:
id: \d+
webkul_custom_credentials_change_status:
path: /custom-configuration/rest/toggle/{id}
defaults: { _controller: customBundle:Rest\Configuration:toggle }
requirements:
id: \d+
webkul_custom_credentials_delete:
path: /custom-configuration/rest/credentials/{id}
defaults: { _controller: customBundle:Rest\Configuration:deleteCredentail }
requirements:
id: \d+
methods: [DELETE]
Define the methods for changestatus, and delete
# Webkul\CustomBundle\Controller\Rest\ConfigurationController
/**
* Remove url
*
* @AclAncestor("webkul_custom_connector_configuration")
*
* @return JsonResponse
*/
public function deleteCredentailAction($id)
{
$mapping = $this->get('webkul_custom.repository.credentials')->find($id);
if(!$mapping) {
throw new NotFoundHttpException(
sprintf('Instance with id "%s" not found', $id)
);
}
$em = $this->getDoctrine()->getManager();
$em->remove($mapping);
$em->flush();
return new Response(null, Response::HTTP_NO_CONTENT);
}
/**
* Activate/Deactivate a credential instance
*
* @param CredentialsConfig $configValue
*
* @AclAncestor("webkul_custom_connector_configuration")
*
* @return JsonResponse
*/
public function toggleAction(CredentialsConfig $configValue)
{
try{
$em = $this->getDoctrine()->getManager();
//change current active value
$configValue->activation();
$em->persist($configValue);
$em->flush();
} catch(Exception $e) {
return new JsonResponse(['route' => 'webkul_custom_connector_configuration']);
}
return new JsonResponse(['route' => 'webkul_custom_connector_configuration']);
}
extensions:
# Attribute Mapping Model
webkul-custom-connector-create-instance-modal:
module: custom/form/configuration/create/modal
config:
labels:
title: webkul_custom_connector.create_popin.title
subTitle: webkul_custom_connector.item.credential
picture: illustrations/Groups.svg
successMessage: pim_enrich.entity.group_type.message.created
editRoute: webkul_custom_credentials_edit
postUrl: webkul_custom_credentials_create
routerKey: id
webkul-custom-connector-create-instance-modal-host:
module: custom/form/configuration/create/form
parent: webkul-custom-connector-create-instance-modal
targetZone: fields
position: 10
config:
labels:
title: webkul_custom_connector.create_popin.title
subTitle: webkul_custom_connector.item.export_mapping
identifier: host
label: webkul_custom_connector.hostName
#src/Webkul/customBundle/Resources/config/requirejs.yml
config:
paths:
custom/form/configuration/create/modal: custom/js/form/configuration/create/modal
custom/form/configuration/create/form: custom/js/form/configuration/create/form
config:
pim/controller-registry:
controllers:
webkul_custom_credentials_change_status:
module: pim/controller/redirect
webkul_custom_credentials_edit:
module: custom/form/configuration/credentials/edit
Create instance js module.
// src/Webkul/customBundle/Resources/public/js/form/configuration/create/modal.js
'use strict';
define(
[
'jquery',
'underscore',
'oro/translator',
'routing',
'pim/form/common/creation/modal',
'oro/loading-mask',
'pim/router',
],
function (
$,
_,
__,
Routing,
BaseModal,
LoadingMask,
router,
) {
return BaseModal.extend({
validationErrors: {},
/**
* {@inheritdoc}
*/
save() {
this.validationErrors = {};
const loadingMask = new LoadingMask();
this.$el.empty().append(loadingMask.render().$el.show());
let data = this.getFormData();
return $.ajax({
url: Routing.generate(this.config.postUrl),
type: 'POST',
data: JSON.stringify(data)
}).fail(function (response) {
this.validationErrors = response.responseJSON;
this.render();
}.bind(this))
.always(() => loadingMask.remove());
}
});
}
);
// src/Webkul/customBundle/Resources/public/js/form/configuration/create
define([
'jquery',
'underscore',
'custom/form/configuration/create/modal',
'pim/user-context',
'oro/translator',
'pim/fetcher-registry',
'pim/initselect2',
'custom/template/configuration/tab/credential',
'routing',
'oro/messenger',
'oro/loading-mask'
], function(
$,
_,
BaseModal,
UserContext,
__,
FetcherRegistry,
initSelect2,
template,
Routing,
messenger,
LoadingMask
) {
return BaseModal.extend({
loadingMask: null,
updateFailureMessage: __('error to fetch token'),
updateSuccessMessage: __('pim_enrich.entity.info.update_successful'),
label: __('pim_enrich.entity.save.label'),
isGroup: true,
label: __('custom.credential.tab'),
template: _.template(template),
code: 'custom_connector_credential',
controls: [{
'label' : 'custom.form.properties.host_name.title',
'name': 'hostName',
'type': 'text'
}, {
'label' : 'custom.form.properties.apiUser.title',
'name': 'apiUser',
'type': 'text'
}, {
'label' : 'custom.form.properties.apiKey.title',
'name': 'apiKey',
'type': 'password'
}],
errors: [],
events: {
'change .AknFormContainer-Credential input': 'updateModel',
},
/**
* {@inheritdoc}
*/
render: function () {
$('#container .AknButtonList[data-drop-zone="buttons"] div:nth-of-type(1)').hide();
self = this;
var controls;
var controls2;
this.$el.html(this.template({
controls: self.controls,
controls2: self.controls2,
model: self.getFormData(),
errors: this.parent.validationErrors
}));
this.delegateEvents();
},
/**
* Update model after value change
*
* @param {Event} event
*/
updateModel: function (event) {
var data = this.getFormData();
switch(event.target.id) {
case 'pim_enrich_entity_form_hostName':
data['hostName'] = event.target.value
break;
case 'pim_enrich_entity_form_apiUser':
data['apiUser'] = event.target.value
break;
case 'pim_enrich_entity_form_apiKey':
data['apiKey'] = event.target.value
}
this.setData(data);
},
stringify: function(formData) {
if('undefined' != typeof(formData['mapping']) && formData['mapping'] instanceof Array) {
formData['mapping'] = $.extend({}, formData['mapping']);
}
return JSON.stringify(formData);
},
/**
* {@inheritdoc}
*/
getSaveUrl: function () {
var route = Routing.generate('webkul_custom_connector_configuration_post');
return route;
},
/**
* Sets errors
*
* @param {Object} errors
*/
setValidationErrors: function (errors) {
this.parent.validationErrors = errors.response;
this.render();
},
/**
* Resets errors
*/
resetValidationErrors: function () {
this.parent.validationErrors = {};
this.render();
},
/**
* Show the loading mask
*/
showLoadingMask: function () {
this.loadingMask = new LoadingMask();
this.loadingMask.render().$el.appendTo(this.getRoot().$el).show();
},
/**
* Hide the loading mask
*/
hideLoadingMask: function () {
this.loadingMask.hide().$el.remove();
},
});
});