In my project (symfony/flex 1.1) i had a method "displayMenuAndSubMenu([...])" (as a service) to build menu and submenus on a multidimensionnal array.
NB: Some menus may not have submenus
My problem is that when I want to render my menu board in a html.twig template,I can not reproduce the incremental variables $i and $a. Indeed, with my actually twig code I get a simple string output :-(
I have read somewhere that a custom twig extension will allow me to work around this difficulty.
But I find it very complicated to implement a simple counter.
So my questions are:
Is there a simpler way to do this in a html.twig template ?
Do I have to go through in a php.twig template (so disgustin in my sense) ?
Am I doomed to define a twig extension ? (And if so, do you have an example ?)
Thanks
ContentService::displayMenuAndSubmenu
public function displayMenuAndSubMenu(CategoryService $categoryService, CategoryTypeService $categoryTypeService)
{
$categoryId = $categoryService->getCategoryId('Primary');
$categoryTypeId = $categoryTypeService->getCategoryTypeId('Menu');
// Getting primary menus
$primaryMenu = $this->em->getRepository('App\Entity\Content')->findByCategoryTypeAndCategory($categoryTypeId, $categoryId);
// Constructing MainMenu with menus and submenus
$i = 0;
$menu = [];
$submenu = [];
$mainMenu = [];
foreach ($primaryMenu as $keyMenu => $menuItem) {
$menu[$i] = [ 'menu_' . $i => [
'id' => $menuItem->getId(),
'name' => $menuItem->getName(),
'body' => $menuItem->getBody(),
'slug' => $menuItem->getSlug(),
]
];
// Getting secondary menu
$secondaryMenu = $this->em->getRepository('App\Entity\Content')->findByParent($menuItem->getId());
$a = 0;
foreach ($secondaryMenu as $keySubmenu => $subMenuItem) {
$submenu[$i][$a] = [ 'submenu_' . $i . '_' . $a => [
'id' => $subMenuItem->getId(),
'name' => $subMenuItem->getName(),
'body' => $subMenuItem->getBody(),
'slug' => $subMenuItem->getSlug(),
]
];
$menu[$i] += $submenu[$i][$a];
$a++;
}
$mainMenu += $menu;
$i++;
}
return $mainMenu;
}
_menu.html.twig
{% set i = 0 %}
{% for menu in menu_items %}
{% set a = 0 %}
<div class="center">
<section class="primary-menu">
{% if 'menu_'~i~'.slug' is not empty %}
<header class="enter">{{ 'menu_'~i~'.name' }}</header>
{% for submenu in 'menu_'~i~'.submenu' %}
<article>
{{ 'submenu_'~i~'_'~a~'.name' }}
</article>
{% endfor %}
{% else %}
<header class="enter">{{ 'menu_'~i~'.name' }}</header>
{% endif %}
</section>
{% set i = i + 1 %}
{% endfor %}
dump($mainMenu) on ContentService class output
array:5 [▼
0 => array:4 [▼
"menu_0" => array:4 [▼
"id" => "a420742f-124a-11e9-a1fd-805e4fe8b43b"
"name" => "Menu 0"
"body" => "Primary menu0"
"slug" => null
]
"submenu_0_0" => array:4 [▼
"id" => "a4208ec3-124a-11e9-a1fd-805e4fe8b43b"
"name" => "Submenu 0"
"body" => "Secondary menu0"
"slug" => "menu-0-submenu-0"
]
"submenu_0_1" => array:4 [▼
"id" => "a420a70d-124a-11e9-a1fd-805e4fe8b43b"
"name" => "Submenu 1"
"body" => "Secondary menu1"
"slug" => "menu-0-submenu-1"
]
"submenu_0_2" => array:4 [▼
"id" => "a420ba8f-124a-11e9-a1fd-805e4fe8b43b"
"name" => "Submenu 2"
"body" => "Secondary menu2"
"slug" => "menu-0-submenu-2"
]
]
1 => array:1 [▼
"menu_1" => array:4 [▼
"id" => "a4205c9f-124a-11e9-a1fd-805e4fe8b43b"
"name" => "Home"
"body" => "Home menu"
"slug" => "home"
]
]
2 => array:4 [▶]
3 => array:4 [▶]
4 => array:4 [▶]
]
Related
I have a array like this:
element 1 => [
#id: "**b26b0d394282a3a2b841137928a0"
#referencedId: "**b26b0d394282a3a2b841137928a0"
#label: "test mit variante"
#quantity: 1
#type: "product"
#payload: array:18 [▼
"parentId" => "**744dc8466b48c255b2ab2"
"cos" => "**744dc8466b48c255b2ab2"
"options" => array:2 [▶]
]
]
element 2 => [
[
#id: "**b26b0d394282a3a2b8343441137928a0"
#referencedId: "**b26b0d394282a3a2b8343441137928a0"
#label: "test 2 mit variante"
#quantity: 1
#type: "product"
#payload: array:18 [▼
"parentId" => "**744dc8466b48c255b2ab2"
"cos" => "**744dc8466b48c255b2ab2"
"isCloseout" => false
"releaseDate" => null
"tagIds" => null
"categoryIds" => array:4 [▶]
"propertyIds" => null
"optionIds" => array:4 [▶]
"options" => array:2 [▶]
"features" => []
]
]
element 3 => [
[
#id: "**b26b0d394282a3a2b841137928a0"
#referencedId: "**b26b0d394282a3a2b841137928a0"
#label: "test 2 mit variante"
#quantity: 1
#type: "product"
#payload: array:18 [▼
"parentId" => "**744dc8466b48c255b2ab2"
"cos" => "**744dc8466b48c255b2ab2"
"isCloseout" => false
"releaseDate" => null
"tagIds" => null
"categoryIds" => array:4 [▶]
"propertyIds" => null
"optionIds" => array:5 [▶]
"options" => array:5 [▶]
"features" => []
]
]
element 4 => [
[
#id: "**b5645646"
#referencedId: "**b5645646"
#label: "test 2 mit variante"
#quantity: 1
#type: "product"
#payload: array:18 [▼
"parentId" => "**71114545"
"cos" => "**71114545"
"isCloseout" => false
"releaseDate" => null
"tagIds" => null
"categoryIds" => array:4 [▶]
"propertyIds" => null
"optionIds" => array:4 [▶]
"options" => array:3 [▶]
"features" => []
]
]
element 5 => [
[
#id: "**b5645634346"
#referencedId: "**b5645634346"
#label: "test 2 mit variante"
#quantity: 1
#type: "product"
#payload: array:18 [▼
"parentId" => "**71114545"
"cos" => "**71114545"
"isCloseout" => false
"releaseDate" => null
"tagIds" => null
"categoryIds" => array:4 [▶]
"propertyIds" => null
"optionIds" => array:4 [▶]
"options" => array:3 [▶]
"features" => []
]
]
I want to somhow filter the elements with the same parentId and show them on the page like this:
test1 - Size: XL, XS, XXL
test2 - Size: XLL, XS, M, XXL
so far i tried using twig filter like this:
https://symfony.com/blog/twig-adds-filter-map-and-reduce-features
{% block custom_layout_products %}
{% for lineitem in page.cart.lineItems | filter(lineitem => lineitem.payload.parentId is same as lineitem.payload.parentId) %}
{{lineitem.label}}
{% endfor %}
% endblock %}
lineitem.payload.parentId is same as lineitem.payload.parentId will return true and i get the all names.
i also tried using loop.first but still no success:
% for lineitem in page.cart.lineItems %}
{% if loop.first and lineitem.payload.parentId == lineitem.payload.parentId %}
{{lineitem.label}}
{% endif %}
does anyone have an idea?
The problem you are facing is that you need to display a list of products with their corresponding variants, e.g. T-Shirt Alpha in red and blue.
At this point you have an array that contains all the variants an user put in their cart. So the first thing you need to do is create a new array that only contains the "main" product, e.g. T-Shirt Alpha.
You could do this in twig as shown in the snippet below, but preferably you would want to do this in your controller
{% set parents = [] %}
{% for lineItem in page.cart.lineItems %}
{% set parents = parents|merge({ (lineItem.payload.parentId) : lineItem, }) %}
{% endfor %}
So lets say you have the following products in the cart:
T-Shirt One - red
T-Shirt Two - blue
T-Shirt One - green
T-Shirt One - yellow
T-Shirt Three - orange
Then parents would only contain:
T-Shirt One
T-Shirt Two
T-Shirt Three
Now you can loop this new (unique) array, which only contains 1 of each of the different "main" products, to display them and their variants (which are still stored in the original array page.cart.lineItems)
To do so, you now can use the parentId of the "main" product to fetch all the variants.
{% for parent in parents %}
<table>
<tr>
<td>Label</td><td>{{ parent.label }}</td>
<tr>
<tr>
<td colspan="2">
<table>
<tr>
<th>Size</th>
<th>Quantity</th>
</tr>
{% for variant in page.cart.lineItems|filter(v => v.payload.parentId == parent.payload.parentId) %}
<tr>
<td>{{ variant.size }}</td>
<td>{{ variant.quantity }}</td>
</tr>
{% endfor %}
</table>
</td>
</tr>
</table>
{% endfor %}
demo
The main difference in my snippet versus yours is that I use an unique list of "main" products of which I use the parentId to narrow down the variants
page.cart.lineItems|filter(v => v.payload.parentId == parent.payload.parentId) %}
In the code you've posted you've used the following
filter(lineitem => lineitem.payload.parentId is same as lineitem.payload.parentId)
Here you are using the same object to try and narrow the list down. But because you are using the same object, this is actually writing the same as the following:
filter(lineitem => 1 == 1)
This will always evaluate as true
With Sonata, when I create a contract with a choiceType, the user can choose contract1 or contract2 and in my database I would get "451" for contract1 and "678" for contract2.
In my Field List all my data are displayed but for my contract I've got either "451" or "678" and I would like instead of those numbers, contract1 or contract2.
This is my field for creating the contract :
$mapper
->add('contract', ChoiceType::class, [
'choices' => [
'contract1' => '451',
'contract2' => '678',
],
])
And in my code for the field, I don't know how to tell it if 451 then 'contract1'. I started like that :
->add('contract', null, [
'label' => 'Contract',
])
Any idea ?
You can use the form entity type which would solve your problem:
$builder->add('contract', EntityType::class, [
// looks for choices from this entity
'class' => Contract::class,
// uses the Contrzct.name property as the visible option string
'choice_label' => 'name',
// Query builder to select your to specific contract
'query_builder' => function (ContractRepositoty $contractRepository) {
return $contractRepository->createQueryBuilder('support_time_slot')
->where('contract.id in :ids')
->setParameter('ids', [461,678])
->orderBy('contract.name');
},
// used to render a select box, check boxes or radios
'multiple' => true,
'expanded' => true,
]);
I found a solution. I created a specific template and in it I translated the value I wanted:
->add('contract', null, [
'label' => 'Contract',
'template' => 'AdminBundle:ContractAdmin:list__operation.html.twig'
])
And my Twig :
{% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}
{% block field %}
{% if value %}
{{ ('contract.operation.'~value~'.value')|trans }}
{% endif %}
{% endblock %}
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 create my form like this:
$siteContent2Form = $this->get('form.factory')->createNamedBuilder('cont_form_2', CKEditorType::class, $siteContent2, array(
'label' => false,
'config_name' => 'ckeditor_config_std',
))->getForm();
in twig:
{{ form_start(form_content_2) }}
{{ form_widget(form_content_2) }}
{{ form_end(form_content_2) }}
Is it possible to override the loaded config "ckeditor_config_std" and load another one in the template (twig)?
I don't think you can do this without creating a Twig extension. Here's what I found out:
I followed Symfony's tutorial to build a simple form with three fields. The task field is a CKEditor field:
$form = $this->createFormBuilder($task)
->add('task', CKEditorType::class, [
'config_name' => 'my_config',
'config' => [
'uiColor' => '#c0ffee',
],
])
->add('dueDate', DateType::class)
->add('save', SubmitType::class, ['label' => 'Create task'])
->getForm();
Let's see what we have in Twig:
{{ dump(form) }}
FormView {#398 ▼
+vars: array:24 [▶]
+parent: null
+children: array:3 [▼
"task" => FormView {#403 ▶}
"dueDate" => FormView {#405 ▶}
"save" => FormView {#327 ▶}
]
-rendered: false
-methodRendered: false
}
See where the configuration options are (the uiColor config comes from PHP as seen above whereas the toolbar config comes from YAML configuration):
FormView {#398 ▼
+children: array:3 [▼
"task" => FormView {#403 ▼
+vars: array:41 [▼
"config" => array:2 [▼
"toolbar" => array:1 [▶]
"uiColor" => "#c0ffee"
]
]
}
]
}
The FormView class doesn't seem to provide any methods for changing the vars: https://api.symfony.com/3.4/Symfony/Component/Form/FormView.html
You can neither use the merge filter because the FormView object will be converted into an array, and then you can't render the form:
{% set form = form|merge({}) %}
{{ dump(form) }}
array:3 [▼
"task" => FormView {#403 ▶}
"dueDate" => FormView {#405 ▶}
"save" => FormView {#327 ▶}
]
You could create a simple Twig extension, something like this:
<?php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class AppExtension extends AbstractExtension
{
public function getFunctions(): array
{
return [
new TwigFunction('set_ckeditor_config', [$this, 'SetCKEditorConfig']),
];
}
public function SetCKEditorConfig($formView, $name, $value)
{
$formView->vars['config'][$name] = $value;
}
}
And then in Twig:
{{ dump(form.task.vars.config) }}
{% do set_ckeditor_config(form.task, 'uiColor', '#bada55') %}
{% do set_ckeditor_config(form.task, 'height', 500) %}
{{ dump(form.task.vars.config) }}
{# You should render the form *after* calling the set_ckeditor_config function #}
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
array:2 [▼
"toolbar" => array:1 [▶]
"uiColor" => "#c0ffee"
]
array:3 [▼
"toolbar" => array:1 [▶]
"uiColor" => "#bada55"
"height" => 500
]
As you can see, this way you can modify single configuration options. This is not exactly what you asked, but it's close, right?
You can modify SetCKEditorConfig so that you set multiple configs at once. Then you would need to call set_ckeditor_config in Twig only once. That might be better if there are many configs that you want to change.
You can also try to modify the code so that you can choose different config_name (i.e. something else than ckeditor_config_std in your case). It seemed to be a more complex thing to do, so I gave up.
I hope this leads you on the right track.
From the default knp menu bundle template:
{%- elseif matcher.isAncestor(item, options.matchingDepth) %}
{%- set classes = classes|merge([options.ancestorClass]) %}
options.ancestorClass is equal to 'current_ancestor'. Is there a way to override this? I dont want to copy the wohle block item code which covers 50 lines of code, from which I only need to change one value.
To apply default options in all your application, you can set the knp_menu.renderer.twig.options parameter like this:
// app/config/services.yml
parameters:
knp_menu.renderer.twig.options:
currentClass: active
Default options of the Knp\Menu\Renderer\TwigRenderer are:
$this->defaultOptions = array_merge(array(
'depth' => null,
'matchingDepth' => null,
'currentAsLink' => true,
'currentClass' => 'current',
'ancestorClass' => 'current_ancestor',
'firstClass' => 'first',
'lastClass' => 'last',
'template' => $template,
'compressed' => false,
'allow_safe_labels' => false,
'clear_matcher' => true,
'leaf_class' => null,
'branch_class' => null,
), $defaultOptions);
Try
{{ knp_menu_render('AcmeDemoBundle:Builder:mainMenu', {'ancestorClass': 'your-class'}) }}
From this link