How can I set-up the an Admin class in SonataAdminBundle, to display the form for adding/editing an entity in a modal window? Like below:
Manually you can do it yourself.
Add in your config service
calls:
- [ setTemplate, [list, AcmeYourBundle:Your:base_list.html.twig]]
- [ setTemplate, [edit, AcmeYourBundle:Your:base_edit.html.twig]]
In your admin bundle add custom templete in configureListFields
protected function configureListFields(ListMapper $listMapper) {
$listMapper
->add('_action', 'actions', array(
'actions' => array(
'edit' => array('template' => 'AcmeYourBundle:Your:_action_edit.html.twig'),
)
));
}
_action_edit.html.twig
{% if admin.hasRoute('edit') and admin.id(object) and admin.isGranted('EDIT', object)%}
<a class="edit sonata-action-element" href="{{ admin.generateObjectUrl('edit', object) }}">
<i class="fa fa-edit"></i>
{{ 'link_action_edit'|trans({}, 'SonataAdminBundle') }}
</a>
{% endif %}
Add javascript code in base_list.html.twig
<script type="text/javascript">
$(document).ready(function() {
$('a.edit').click(function() { //bind handlers
var url = $(this).attr('href');
showDialog(url);
return false;
});
$("#targetDiv").dialog({
autoOpen: false,
height: 700,
width: 950,
modal: true
});
function showDialog(url) {
$("#targetDiv").load(url);
$("#targetDiv").dialog("open");
}
});
</script>
Done ! Enjoy it.
This is not supported yet, this is a planned feature but no ETA for now.
Related
I'm using the kevinPast adminLte bundle on symfony 5. Every datetype got their own format(dd-MM-YYYY) and you can't update it as a normal way by writing 'format' => 'yyyy-MM-dd' inside the form for example. The problem occurs also for other datetype, like birthday.
I also try to use my own datepicker function to override the existing one, but it's a bundle, so it first not work, then it broke also the existing js.
You can see the different try i did:
->add('startTime', DateTimeType::class, [
'required' => true,
'error_bubbling' => false,
'date_widget' => 'single_text',
// #todo Can we make this format configurable?
'date_format' => 'yyyy-MM-dd', // must match data-date-format as defined in field.html.twig
'minutes' => range(0, 55, 5)
])
it will show the data like that
it use the block date_widget on twig, the single_text area
{% block date_widget %}
{% if widget == 'single_text' %}
<div class="input-group">
<div class="input-group-addon">
<i class="far fa-calendar-alt"></i>
</div>
{% if type is not defined or type != 'date' %}
{% if attr.class is defined %}
{% set class = attr.class ~ ' timepicker' %}
{% else %}
{% set class = ' timepicker' %}
{% endif %}
{% set attr = attr|merge({'class' : class, 'data-datepickerenable':'on'}) %}
{% endif %}
{{ block('form_widget_simple') }}
</div>
{% else %}
{% set date_pattern = '<div class="row">' ~ date_pattern ~ '</div>'|raw %}
{{ date_pattern|replace({
'{{ year }}' : '<div class="col-xs-4">{{ year }}</div>',
'{{ month }}' : '<div class="col-xs-4">{{ month }}</div>',
'{{ day }}' : '<div class="col-xs-4">{{ day }}</div>',
})|raw|replace({
'{{ year }}': form_widget(form.year),
'{{ month }}': form_widget(form.month),
'{{ day }}': form_widget(form.day),
})|raw }}
{% endif %}
{% endblock %}
I also try to override it by using the following code:
Js part:
$(document).ready(function() {
$('.datepicker').datepicker({
format: "DD/MM/YYYY h:mm"
});
});
form part
->add('startTime', DateTimeType::class, [
'required' => true,
'error_bubbling' => false,
'date_widget' => 'single_text',
//<input type="text" class="form-control" data-inputmask-alias="datetime" data-inputmask-inputformat="dd/mm/yyyy" data-mask="" im-insert="false">
// #todo Can we make this format configurable?
'date_format' => 'yyyy-MM-dd', // must match data-date-format as defined in field.html.twig
'minutes' => range(0, 55, 5),
'attr' => ['class' => 'datepicker'],
])
This one overide the actual datepicker, but first doesn't fix the problem(you still have the standard format DD-MM-YYY) then it also broke the js used by the bundle(some navigation button doesn't work etc)
If I found a fix, i will also write the answer here.
So after few work on it and if someone got the same problem, I found one solution, maybe not the best but it's work.
I checked the demo used with adminLte bundle on symfony (Kimai2)
So to be able to edit your calendar and format your date, you need to follow these step:
1 : create a dateTimePickerType and use it instead of default DatetimeType
<?php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DateTimePickerType extends AbstractType
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['attr'] = array_merge($view->vars['attr'], [
'data-datetimepicker' => 'on',
'autocomplete' => 'off',
'placeholder' => strtoupper($options['format']),
'data-format' => $options['format_picker'],
'data-time-picker-increment' => $options['time_increment'],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'label' => '',
'widget' => 'single_text',
'html5' => false,
'format' => 'YYYY-MM-DD',
'format_picker' => 'YYYY-MM-DD',
'with_seconds' => false,
'time_increment' => 1,
]);
}
/**
* {#inheritdoc}
*/
public function getParent()
{
return DateTimeType::class;
}
}
and on the formType
->add('startTime', DateTimePickerType::class, [
'required' => true,
'error_bubbling' => false,
'label' => 'startTime',
'format' => "yyyy-MM-dd'T'HH:mm:ss",
'attr' => ['class' => 'js-datepicker-hours'],
])
Then the javascript part, you need to install daterangerpicker from www.daterangepicker.com
and use these following function:
const $ = require('jquery');
global.$ = global.jQuery = $;
const Moment = require('moment');
global.moment = Moment;
require('moment/locale/en-gb');
require('daterangepicker');
$(document).ready(function() {
$('.js-datepicker').daterangepicker({
language: "en",
singleDatePicker: true,
showDropdowns: true,
minYear: 1901,
//maxYear: parseInt(moment().format('YYYY'),10)
});
$('.js-datepicker-hours').daterangepicker({
language: "en",
timePicker: true,
singleDatePicker: true,
showDropdowns: true,
locale: {
format: 'yyyy-MM-DD HH:mm:ss'
}
//maxYear: parseInt(moment().format('YYYY'),10)
});
I'm trying to submit a form which is supposed to deactivate several documents. When hitting the "Submit" button nothing happens.
Here is my twig file:
{% block content %}
{{ form_start(form) }}
{{ form(form.documentlist) }}
{{ form_end(form) }}
<p>
<div class="row">
<button type="submit">Submit</button>
</div>
{% endblock content %}
{% block javascripts %}
{{ parent() }}
<script src="{{ asset('js/bootstrap-multiselect.js') }}"></script>
<script>
makeMultiselectDropdown('#{{ form.vars.name }}_documentlist', 'Select Document');
</script>
<script type="text/javascript">
$(document).ready(function() {
$('#{{ form.vars.name }}_documentlist').change(function() {
var docId = $('#{{ form.vars.name }}_documentlist').val();
$.ajax({
type: "POST",
data: {'id': docId},
});
});
});
</script>
{% endblock %}
and my Controller function:
/**
* #Route("/document/bulkdeactivate", name="documentBundle_document_bulkDeactivate")
* #Template()
*/
public function bulkDeactivateAction(Request $request) {
$em = $this->getDoctrine()->getManager();
$selected_documents = $request->request->all();
$form = $this->createForm(DocumentDeactivationType::class);
$form->handleRequest($request);
if ($form->isValid() && $form->isSubmitted()) {
foreach($selected_documents as $document) {
$documentR = json_decode(json_encode($document), true);
dump($documentR);
for($i=0; $i<count($documentR); $i++){
$doc = $em->getRepository('DocumentBundle:Document')->findOneById($documentR[$i]);
dump($doc);
$document->setActive(false);
$em->persist($document);
$em->flush();
}
}
$this->addFlash(
'success',
'The document has been deactivated!'
);
return $this->redirectToRoute('documentBundle_document_list');
}
return $this->render('DocumentBundle:Panels:ActivationPanel.html.twig', array(
'form' => $form->createView(),
));
}
I looked in the console while hitting "Submit" and it says
Uncaught TypeError: Cannot set property 'href' of null
at window.onload (bulkdeactivate:257)
which is the line that renders the twig template. I don't know if this has anything to do with my problem, just wanted to let you know as much as I know!
Any help would be appreciated!
I try to follow this link to install and create a form with CraueFormFlowBundle
CraueFormFlowBundle tutorial
i create the file
/src/AppBundle/Form/InterventoFlow.php
namespace AppBundle/Form;
use Craue\FormFlowBundle\Form\FormFlow;
use Craue\FormFlowBundle\Form\FormFlowInterface;
class InterventoFlow extends FormFlow {
protected function loadStepsConfig() {
return array(
array(
'label' => 'wheels',
'form_type' => 'AppBundle\Form\InterventoForm',
),
array(
'label' => 'engine',
'form_type' => 'AppBundle\Form\InterventoForm',
'skip' => function($estimatedCurrentStepNumber, FormFlowInterface $flow) {
return $estimatedCurrentStepNumber > 1 && !$flow->getFormData()->canHaveEngine();
},
),
array(
'label' => 'confirmation',
),
);
}
}
after i create
/src/AppBundle/Form/InterventoForm.php
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
namespace AppBundle/Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
class InterventoForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
switch ($options['flow_step']) {
case 1:
$validValues = array(2, 4);
$builder->add('numberOfWheels', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array(
'choices' => array_combine($validValues, $validValues),
'placeholder' => '',
));
break;
case 2:
// This form type is not defined in the example.
$builder->add('engine', 'TextType', array(
'placeholder' => 'prova',
));
break;
}
}
public function getBlockPrefix() {
return 'createIntervento';
}
}
after i create the service
services:
app.form.InterventoFlow:
class: AppBundle\Form\InterventoFlow
parent: craue.form.flow
and the controller
public function interventoAction(Request $request)
{
$formData = new Validate(); // Your form data class. Has to be an object, won't work properly with an array.
$flow = $this->get('AppBundle.form.InterventoFlow'); // must match the flow's service id
$flow->bind($formData);
// form of the current step
$form = $flow->createForm();
if ($flow->isValid($form)) {
$flow->saveCurrentStepData($form);
if ($flow->nextStep()) {
// form for the next step
$form = $flow->createForm();
} else {
// flow finished
$em = $this->getDoctrine()->getManager();
$em->persist($formData);
$em->flush();
$flow->reset(); // remove step data from the session
return $this->redirect($this->generateUrl('home')); // redirect when done
}
}
return $this->render('interventi/intervento.html.twig', array(
'form' => $form->createView(),
'flow' => $flow,
));
}
and my twig file
{% extends 'base.html.twig' %}
{% block body %}
<div>
Steps:
{% include '#CraueFormFlow/FormFlow/stepList.html.twig' %}
</div>
{{ form_start(form) }}
{{ form_errors(form) }}
{% if flow.getCurrentStepNumber() == 1 %}
<div>
When selecting four wheels you have to choose the engine in the next step.<br />
{{ form_row(form.numberOfWheels) }}
</div>
{% endif %}
{{ form_rest(form) }}
{% include '#CraueFormFlow/FormFlow/buttons.html.twig' %}
{{ form_end(form) }}
{% endblock %}
{% stylesheets '#CraueFormFlowBundle/Resources/assets/css/buttons.css' %}
<link type="text/css" rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}
always give me error
Attribute "autowire" on service "app.form.intervento" cannot be inherited from "_defaults" when a "parent" is set. Move your child definitions to a separate file or define this attribute explicitly in C:\xampp\htdocs\Myprject\app/config\services.yml (which is being imported from "C:\xampp\htdocs\Myprject\app/config\config.yml").
and if i comment
parent: craue.form.flow
You have requested a non-existent service "app.form.interventoFlow".
Try to dalete _defaults section from your service.yml file.
I am making the form which allow a user to select a img.
Symfony2.8 with sonataMediaBundle/
$form = $this->createFormBuilder($form)
->add('media',EntityType::class,array(
'expanded' => true,
'class' => "Application\Sonata\MediaBundle\Entity\Media"
}))
->add('save', SubmitType::class, array('label' => 'Create Post'))
->getForm();
in twig
{{form_widget(form.media)}}
However it shows the radio buttons with only the name of img.
○AAA.jpg ○BBB.jpg ○CCC.jpg ○DDD.jpg ('○' is radio button)
It is not good design for users.
I want to show the thumbnail of imgs here.
Is there good way to do this?
The easiest way i can imagine is to actually check the radio button with javascript.
Your Controller :
//src/AppBundle/Controller/YourController.php
public function yourAction()
{
$em = $this->getDoctrine()->getManager();
$medias = $em->getRepository('AppBundle:Media')->findAll();
$entity = new YourEntity();
$form = $this->createForm(YourEntityType::class, $entity);
if ($form->isSubmitted() && $form->isValid()) {
//... your logic
}
return $this->render('AppBundle::template.html.twig', array(
'form' => $form->createView(),
'medias' => $medias
));
}
Then in your twig file
{% for media in medias %}
<img class="to-select" src="{{ media.pathToThumbnail }}" data-id="{{ media.id }}" />
{% endfor %}
{{ form_start(form) }}
<!-- assuming you are using bootstrap -->
<div class="hidden">{{ form_widget(form.media) }}</div>
{{ form_widget(form.submit) }}
{{ form_end(form) }}
{% block javascripts %}
<script>
//assuming you use jquery
$('.to-select').click(function () {
var id = '#form_name_media_' + $(this).data('id');
var media = $(id);
if (media.is(':checked')) {
media.prop('checked', false);
} else {
media.prop('checked', true);
}
});
</script>
{% endblock javascripts %}
I am struggling with Symfony2 and Knpmenu to build a menu that handles:
breadcrumbs
routing with dynamic parameters
rendering of separate menus starting with different children
My Menu/Builder.php file looks like this (the extra bits like navbar, pull-nav etc are part of the mopa_bootstrap extension that handles the rendering using bootstrap classes):
namespace My\AppBundle\Menu;
use Knp\Menu\FactoryInterface;
class Builder
{
public function mainMenu(FactoryInterface $factory, array $options)
{
$menu = $factory->createItem(
'root', array(
'navbar' => true,
'pull-right' => true,
)
);
// Main Menu -> Config
// no link here, it's just a placeholder
$dropdown = $menu->addChild(
'Config', array(
'dropdown' => true,
'caret' => true,
)
);
// Menu -> Config -> User
$dropdown2 = $dropdown ->addChild(
'User', array(
'route' => 'user',
)
);
// Secondary Menu -> Edit (but child of Menu -> Config -> User)
$dropdown2->addChild(
'Edit',
array(
'route' => 'user_edit',
'routeParameters' => array('name' => $options['id']),
)
);
The idea is to have a main menu that prints the first two levels only, and a separate menu that gets rendered somewhere else to allow users moving between the edit/delete/whatever views of that specific element being viewed.
What I am trying to achieve, is to have a single structure thus to handle the parenting structure, not only to have sign parents as active in the menu, but also to be able to handle a working breadcrumb structure.
InResources/views/base.html.twig I am calling the main menu like this:
{{ mopa_bootstrap_menu('MyAppBundle:Builder:mainMenu', {depth: 2}) }}
And ideally the sub menu like this:
{% set id = app.request.attributes.get('id') %}
{% if app.request.attributes.get('_route') starts with 'user_' %}
{% set menu = knp_menu_get('MyAppBundle:Builder:mainMenu', ['User'], {'id': id }) %}
{{ knp_menu_render(menu) }}
{% endif %}
However:
knpmenu returns error when rendering the main menu, as $options['id'] isn't defined
I still can't render the secondary menu (therefore by passing the parameter 'User') - the page just returns a black output in that block
Is this approach correct?
I'm using "knplabs/knp-menu": "2.0.*#dev" and "symfony/symfony": "2.5.*"
As it turns out, using this method allowed to access effectively $request and add the conditional in the MenuBuilder.php file to prompt the sub-sub-items only under certain conditions.
The final code looks like this:
// Menu/MenuBuilder.php
namespace My\AppBundle\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\HttpFoundation\Request;
class MenuBuilder
{
private $factory;
/**
* #param FactoryInterface $factory
*/
public function __construct(FactoryInterface $factory)
{
$this->factory = $factory;
}
public function createMainMenu(Request $request)
{
$menu = $this->factory->createItem(
'root', array(
'navbar' => true,
'pull-right' => true,
)
);
// Main Menu -> Config
// no link here, it's just a placeholder
$dropdown = $menu->addChild(
'Config', array(
'dropdown' => true,
'caret' => true,
)
);
// Menu -> Config -> User
$dropdown2 = $dropdown ->addChild(
'User', array(
'route' => 'user',
)
);
// Secondary Menu -> Edit (but child of Menu -> Config -> User)
// Skip this part if we are not in the relevant page, thus to avoid errors
if (false !== strpos($request->get('_route'), 'user_') && $request->get('id')):
$dropdown2->addChild(
'Edit',
array(
'route' => 'user_edit',
'routeParameters' => array('id' => $request->get('id')),
)
);
endif;
And the services configuration file:
// Resources/config/services.yml
services:
my_app.menu_builder:
class: My\AppBundle\Menu\MenuBuilder
arguments: ["#knp_menu.factory"]
my_app.menu.main:
class: Knp\Menu\MenuItem
factory_service: my_app.menu_builder
factory_method: createMainMenu
arguments: ["#request"]
scope: request
tags:
- { name: knp_menu.menu, alias: main }
And then the Resources/views/base.html.twig template:
// Main menu
{% set menu = knp_menu_get('main', ['Config', 'Users'], {'id': id }) %}
{{ knp_menu_render(menu) }}
// Secondary menu
{% if app.request.attributes.get('_route') starts with 'user_' %}
{% set menu = knp_menu_get('main', ['Config', 'Users'], {'id': id }) %}
{{ mopa_bootstrap_menu(menu, {'automenu': 'navbar'}) }}
{% endif %}
The solution seems to work, but if you have a better approach please do let me know!