Skip to content

Commit

Permalink
Merge d856875 into 4543163
Browse files Browse the repository at this point in the history
  • Loading branch information
alterphp committed Oct 9, 2018
2 parents 4543163 + d856875 commit 31330a1
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 4 deletions.
18 changes: 18 additions & 0 deletions README.md
Expand Up @@ -232,6 +232,24 @@ easy_admin:

Use following __template_options__ to build your own embedded list (see `field_embedded_list.html.twig`) : entity_fqcn, parent_entity_property, filters, entity, sort.

### Autocomplete add new option 'create' for modal in new and edit

#### Configure form type 'easyadmin_autocomplete', add type_options: { attr: { create: true } }

```yaml
easy_admin:
entities:
Promoter:
class: AppBundle\Entity\Promoter
Event:
class: Tm\EventBundle\Entity\Event
form:
fields:
# ...
- { property: 'promoter', type: 'easyadmin_autocomplete', type_options: { attr: { create: true } } }

```

### Define access permissions

#### Global minimum role access
Expand Down
52 changes: 49 additions & 3 deletions src/Controller/AdminController.php
Expand Up @@ -4,6 +4,7 @@

use EasyCorp\Bundle\EasyAdminBundle\Controller\AdminController as BaseAdminController;
use EasyCorp\Bundle\EasyAdminBundle\Event\EasyAdminEvents;
use Symfony\Component\HttpFoundation\JsonResponse;

class AdminController extends BaseAdminController
{
Expand All @@ -30,9 +31,19 @@ protected function embeddedListAction()
*/
protected function isActionAllowed($actionName)
{
// autocomplete and embeddedList action are mapped to list action for access permissions
if (in_array($actionName, ['autocomplete', 'embeddedList'])) {
$actionName = 'list';
switch ($actionName) {
// autocomplete action is mapped to list action for access permissions
case 'autocomplete':
// embeddedList action is mapped to list action for access permissions
case 'embeddedList':
$actionName = 'list';
break;
// newAjax action is mapped to new action for access permissions
case 'newAjax':
$actionName = 'new';
break;
default:
break;
}

// Get item for edit/show or custom actions => security voters may apply
Expand All @@ -44,4 +55,39 @@ protected function isActionAllowed($actionName)

return parent::isActionAllowed($actionName);
}

/**
* The method that is executed when the user performs a 'new ajax' action on an entity.
*
* @return JsonResponse
*/
protected function newAjaxAction()
{
$this->dispatch(EasyAdminEvents::PRE_NEW);

$entity = $this->executeDynamicMethod('createNew<EntityName>Entity');
$easyadmin = array_merge($this->request->attributes->get('easyadmin'), array('item' => $entity));
$this->request->attributes->set('easyadmin', $easyadmin);

$fields = $this->entity['new']['fields'];
$newForm = $this->executeDynamicMethod('create<EntityName>NewForm', [$entity, $fields]);
$newForm->handleRequest($this->request);
if ($newForm->isSubmitted() && $newForm->isValid()) {
$this->dispatch(EasyAdminEvents::PRE_PERSIST, ['entity' => $entity]);
$this->executeDynamicMethod('persist<EntityName>Entity', [$entity]);
$this->dispatch(EasyAdminEvents::POST_PERSIST, ['entity' => $entity]);

return new JsonResponse(['option' => ['id' => $entity->getId(), 'text' => (string) $entity]]);
}

$this->dispatch(EasyAdminEvents::POST_NEW, ['entity_fields' => $fields, 'form' => $newForm, 'entity' => $entity]);

$parameters = ['form' => $newForm->createView(), 'entity_fields' => $fields, 'entity' => $entity];
$templatePath = '@EasyAdminExtension/default/new_ajax.html.twig';
if (isset($this->entity['templates']['new_ajax'])) {
$templatePath = $this->entity['templates']['new_ajax'];
}

return new JsonResponse(['html' => $this->renderView($templatePath, $parameters)]);
}
}
42 changes: 42 additions & 0 deletions src/Form/Type/Extension/EasyAdminAutocompleteTypeExtension.php
@@ -0,0 +1,42 @@
<?php

namespace AlterPHP\EasyAdminExtensionBundle\Form\Type\Extension;

use EasyCorp\Bundle\EasyAdminBundle\Form\Type\EasyAdminAutocompleteType;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Translation\TranslatorInterface;

class EasyAdminAutocompleteTypeExtension extends AbstractTypeExtension
{
private $router;
private $translator;

public function __construct(RouterInterface $router, TranslatorInterface $translator)
{
$this->router = $router;
$this->translator = $translator;
}

public function getExtendedType()
{
return EasyAdminAutocompleteType::class;
}

public function buildView(FormView $view, FormInterface $form, array $options)
{
if (isset($options['attr']['create']) && $options['attr']['create']) {
$view->vars['attr']['data-easyadmin-autocomplete-create-action-url'] = $this->router->generate(
'easyadmin', array('action' => 'newAjax', 'entity' => $view->vars['autocomplete_entity_name'])
);
$view->vars['attr']['data-easyadmin-autocomplete-create-field-name'] = $view->vars['name'];
$view->vars['attr']['data-easyadmin-autocomplete-create-button-text'] = $this->translator->trans(
'action.add_new_item', array(), 'EasyAdminBundle'
);

unset($view->vars['attr']['create']);
}
}
}
9 changes: 8 additions & 1 deletion src/Resources/config/services.xml
Expand Up @@ -76,9 +76,16 @@
<argument type="service" id="alterphp.easyadmin_extension.helper.menu"/>
<tag name="twig.extension"/>
</service>
<service id="alterphp.easyadmin_extension.form.unauthorized.field" class="AlterPHP\EasyAdminExtensionBundle\Form\Type\Configurator\UnauthorizedFieldConfigurator">
<service id="alterphp.easyadmin_extension.form.type.configurator.unauthorized.field" class="AlterPHP\EasyAdminExtensionBundle\Form\Type\Configurator\UnauthorizedFieldConfigurator">
<argument type="service" id="security.authorization_checker"/>
<tag name="easyadmin.form.type.configurator" />
</service>

<!-- easyadmin extension -->
<service id="alterphp.easyadmin_extension.form.type.extension.autocomplete" class="AlterPHP\EasyAdminExtensionBundle\Form\Type\Extension\EasyAdminAutocompleteTypeExtension">
<argument type="service" id="router"/>
<argument type="service" id="translator"/>
<tag name="form.type_extension" extended-type="EasyCorp\Bundle\EasyAdminBundle\Form\Type\EasyAdminAutocompleteType"/>
</service>
</services>
</container>
100 changes: 100 additions & 0 deletions src/Resources/public/js/autocomplete-create.js
@@ -0,0 +1,100 @@
function createAutoCompleteCreateFields() {
var autocompleteCreateFields = $('[data-easyadmin-autocomplete-create-action-url]');

autocompleteCreateFields.each(function () {
var $this = $(this),
url = $this.data('easyadmin-autocomplete-url'),
url_action = $this.data('easyadmin-autocomplete-create-action-url'),
field_name = $this.data('easyadmin-autocomplete-create-field-name'),
button_text = $this.data('easyadmin-autocomplete-create-button-text'),
select_id = $this.attr('id');

$this.select2({
theme: 'bootstrap',
ajax: {
url: url,
dataType: 'json',
delay: 250,
data: function (params) {
return { 'query': params.term, 'page': params.page };
},
// to indicate that infinite scrolling can be used
processResults: function (data, params) {
return {
results: data.results,
pagination: {
more: data.has_next_page
}
};
},
cache: true
},
placeholder: '',
allowClear: true,
minimumInputLength: 1,
language: {
noResults: function () {
return '<a href="#" class="btn btn-info" onclick="switchToEntityCreation(\''+url_action+'\', \''+select_id+'\', \''+field_name+'\');return false;">'+button_text+' '+field_name+'</a>';
}
},
escapeMarkup: function (markup) {
return markup;
}
});
});
}

function switchToEntityCreation(url_action, select_id, field_name) {
$('#'+select_id).select2('close');
$.ajax({
url : url_action,
type: 'GET',
success: function(data) {
openCreateEntityModal(data, url_action, field_name, select_id);
$('#create-entity-modal').modal({ backdrop: true, keyboard: true });
}
});
}

function openCreateEntityModal(data, url_action, field_name, select_id) {
$('#create-entity-modal .modal-body').html(data.html);
$('form[name="'+field_name+'"]').attr('action', url_action);
initCreateEntityAjaxForm(field_name, select_id);
}

function initCreateEntityAjaxForm(field_name, select_id) {
$('form[name="'+field_name+'"]').submit(function( event ) {
event.preventDefault();
var url_action = $(this).attr('action');
$.ajax({
url: url_action,
type: $(this).attr('method'),
data: $(this).serialize(),
cache: false,
contentType: false,
processData: false,
success: function(data) {
if (data.hasOwnProperty('option')) {
$('#create-entity-modal').modal('hide');
var newOption = new Option(data.option.text, data.option.id, true, true);
$('#'+select_id).append(newOption).trigger('change');
// manually trigger the `select2:select` event
$('#'+select_id).trigger({
type: 'select2:select',
params: { data: data.option }
});
}
if (data.hasOwnProperty('html')) {
openCreateEntityModal(data, url_action, field_name, select_id);
}
},
error: function(error){
console.log(error);
}
});
});
}

$(function () {
createAutoCompleteCreateFields();
});
7 changes: 7 additions & 0 deletions src/Resources/views/default/layout.html.twig
Expand Up @@ -19,4 +19,11 @@
_trans_parameters: _trans_parameters|default([]),
}, with_context = false) }}
{% endblock confirm_modal %}

{% block create_entity_modal %}
{{ include('@EasyAdmin/includes/_create_entity_modal.html.twig', {
_translation_domain: _entity_config.translation_domain|default(easyadmin_config('translation_domain')),
_trans_parameters: _trans_parameters|default([]),
}, with_context = false) }}
{% endblock create_entity_modal %}
{% endblock %}
41 changes: 41 additions & 0 deletions src/Resources/views/default/new_ajax.html.twig
@@ -0,0 +1,41 @@
{% extends '@BaseEasyAdmin/default/new.html.twig' %}

{% block head_stylesheets %}
<style>
#create-entity-modal .main-sidebar {
display: none !important;
}
#create-entity-modal .content-wrapper,
#create-entity-modal .main-footer {
margin-left: 0 !important;
}
</style>
{% endblock %}

{% block head_favicon %}{% endblock %}
{% block head_javascript %}{% endblock %}
{% block header %}{% endblock %}
{% block sidebar %}{% endblock %}
{% block confirm_modal %}{% endblock %}
{% block create_entity_modal %}{% endblock %}
{% block body_javascript %}
{% set _select2_locales = ['ar', 'az', 'bg', 'ca', 'cs', 'da', 'de', 'el', 'en', 'es', 'et', 'eu', 'fa', 'fi', 'fr', 'gl', 'he', 'hi', 'hr', 'hu', 'id', 'is', 'it', 'ja', 'km', 'ko', 'lt', 'lv', 'mk', 'ms', 'nb', 'nl', 'pl', 'pt-BR', 'pt', 'ro', 'ru', 'sk', 'sr-Cyrl', 'sr', 'sv', 'th', 'tr', 'uk', 'vi', 'zh-CN', 'zh-TW'] %}
{% set _app_language = app.request.locale|split('-')|first|split('_')|first %}
{% set _select2_locale = app.request.locale in _select2_locales
? app.request.locale
: _app_language in _select2_locales ? _app_language : 'en'
%}

<script src="{{ asset('bundles/easyadmin/javascript/select2/i18n/' ~ _select2_locale ~ '.js') }}"></script>

<script type="text/javascript">
$(function() {
// Select2 widget is only enabled for the <select> elements which
// explicitly ask for it
$('#create-entity-modal').find('form select[data-widget="select2"]').select2({
theme: 'bootstrap',
language: '{{ _select2_locale }}'
});
});
</script>
{% endblock %}
9 changes: 9 additions & 0 deletions src/Resources/views/includes/_create_entity_modal.html.twig
@@ -0,0 +1,9 @@
<div id="create-entity-modal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body"></div>
</div>
</div>
</div>

<script src="{{ asset('bundles/easyadminextension/js/autocomplete-create.js') }}"></script>
28 changes: 28 additions & 0 deletions tests/Controller/AutocompleteAddTest.php
@@ -0,0 +1,28 @@
<?php

namespace AlterPHP\EasyAdminExtensionBundle\Tests\Controller;

use AlterPHP\EasyAdminExtensionBundle\Tests\Fixtures\AbstractTestCase;

class AutocompleteAddTest extends AbstractTestCase
{
public function setUp()
{
parent::setUp();

$this->initClient(array('environment' => 'autocomplete_add'));
}

public function testNewEntityAutocompleteModal()
{
$crawler = $this->requestNewView('Product');
$this->assertSame(1, $crawler->filter('select#product_category_autocomplete[data-easyadmin-autocomplete-create-action-url="/admin/?action=newAjax&entity=Category"]')->count());

$crawlerAjax = $this->requestNewAjaxView('Category');
$form = $crawlerAjax->filter('form[name=category]')->form(
['category[name]' => 'New Ajax Category']
);
$crawlerAjax = $this->client->submit($form);
$this->assertSame(200, $this->client->getResponse()->getStatusCode());
}
}
13 changes: 13 additions & 0 deletions tests/Fixtures/AbstractTestCase.php
Expand Up @@ -133,4 +133,17 @@ protected function requestEditView($entityName = 'Category', $entityId = '200')
'id' => $entityId,
));
}

/**
* @return Crawler
*/
protected function requestNewAjaxView($entityName = 'Category')
{
$this->getBackendPage(array(
'action' => 'newAjax',
'entity' => $entityName,
));
$response = json_decode($this->client->getResponse()->getContent(), true);
return new Crawler($response['html'], $this->client->getRequest()->getUri());
}
}
15 changes: 15 additions & 0 deletions tests/Fixtures/App/config/config_autocomplete_add.yml
@@ -0,0 +1,15 @@
imports:
- { resource: config.yml }

easy_admin:
entities:
Product:
class: AppTestBundle\Entity\FunctionalTests\Product
form:
fields:
- { property: 'category', type: 'easyadmin_autocomplete', type_options: { attr: { create: true } } }
Category:
class: AppTestBundle\Entity\FunctionalTests\Category
form:
fields:
- name

0 comments on commit 31330a1

Please sign in to comment.