I'm making my first Symfony web app.
Now after creating a couple of forms I found some problem creating a form with a collection of choises.
Creating a collection of TextTypes is very easy and is working in a couple of my forms. Now when I create a collections of ChoiseTypes it does not show anything on the screen. After checking the html code it only show the select statement without any options. Also the prototype contains only the select and not the options.
Where is how I add the collections of choises to the form:
->add('fieldTypes', CollectionType::class, array(
'entry_type' => ChoiceType::class,
'entry_options' => array(
'choices' => $fieldTypes,
'required' => true,
),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
))
And this is the twig part of the sollution:
<td>
<ul id="typeTable" data-prototype="{{ form_widget(form.fieldTypes.vars.prototype)|e }}">
{% for fieldType in form.fieldTypes %}
<li>
{{ form_widget(fieldType) }}
</li>
{% endfor %}
</ul>
</td>
And the full JS code:
<script type="text/javascript">
// keep track of how many email fields have been rendered
var nameCount = '{{ form.fieldNames|length }}';
var typeCount = '{{ form.fieldTypes|length }}';
var optionCount = '{{ form.fieldOptions|length }}';
jQuery(document).ready(function () {
jQuery('#add-new-field').click(function (e) {
e.preventDefault();
var nameList = jQuery('#nameTable');
var typeList = jQuery('#typeTable');
var optionList = jQuery('#optionTable');
// grab the prototype template
var newNameWidget = nameList.attr('data-prototype');
var newTypeWidget = typeList.attr('data-prototype');
var newOptionWidget = optionList.attr('data-prototype');
//var newTypeWidget = jQuery('#fieldType_prototype');
// replace the "__name__" used in the id and name of the prototype
// with a number that's unique to your emails
// end name attribute looks like name="contact[emails][2]"
newNameWidget = newNameWidget.replace(/__name__/g, nameCount);
newTypeWidget = newTypeWidget.replace(/__name__/g, typeCount);
newOptionWidget = newOptionWidget.replace(/__name__/g, optionCount);
var $options = $("#fieldType_prototype > option").clone();
//$(newTypeWidget).append($options);
$("#form_fieldTypes_" + typeCount).html( $("#fieldType_prototype").html() );
// newTypeWidget.attr("id","form_fieldTypes_" + typeCount);
// newTypeWidget.attr('name', 'form[fieldTypes][' + typeCount + ']');
nameCount++;
typeCount++;
optionCount++;
// create a new list element and add it to the list
var newLi = jQuery('<li></li>').html(newNameWidget);
newLi.appendTo(nameList);
var newLi = jQuery('<li></li>').html(newTypeWidget);
newLi.appendTo(typeList);
var newLi = jQuery('<li></li>').html(newOptionWidget);
newLi.appendTo(optionList);
});
})
</script>
Variable $fieldTypes contains this:
array('Text Field' => TextType::class,
'Text area' => TextareaType::class,
'Email' => EmailType::class,
'Number' => IntegerType::class,
'Url' => UrlType::class,
'Drop down' => ChoiceType::class,
'Date' => DateType::class,
'Checkbox' => CheckboxType::class);
}
Can someone help me with this issue?
It was a bug:
xabbuh:
'I guess you might be affected by a regression that will be fixed by github.com/symfony/symfony/pull/17162. Can you test if the changes from that PR solve your problem?'
Related
We've a Drupal 9 installation and are trying to add a pager using the pagerer module for articles entityQuery, the aim is to list tagged articles in a tag page, but it’s not working. It returns null.
When we dump the data without the pager, using default drupal query, it returns the data of all tagged articles properly.
The code is added in the theme file themeName_preprocess_page hook and being called in page--page.html.twig template file.
Here’s our code:
$query = \Drupal::entityQuery('node')
->condition('status', 1)
->condition('type', 'article');
->pager(2);
$nids = $query->sort('created', 'DESC')
->execute();
if($nids):
$nodesNews = \Drupal\node\Entity\Node::loadMultiple($nids);
$pathNews = base_path();
$pager = [
'articles_data' => $nodesNews,
'results' => [
'#theme' => 'news_pagination',
'#items' => $nodesNews,
'#path' => $pathNews,
'#tag' => $tag
],
'pager' => [
'#type' => 'pager',
'#quantity' => 5
],
];
return $pager;
endif;
And here is the code that calls the query:
<div>
{{ articles_data }}
{{ pager }}
</div>
The above code returns only one page in the navigation and we’ve 10 articles, so given that we set 2 articles per page, the output should be 5 pages instead of 1.
Also articles_data attribute returns null. Could you please help me to find what’s wrong with the code? Happy to share more information as needed, thank you.
Just reading the docs for this module here,
it would seem that you are missing at least the #theme and #style keys in your render array for the pager.
A more likely to succeed version of the above render array would be
$pager = [
'articles_data' => $nodesNews,
'results' => [
'#theme' => 'news_pagination',
'#items' => $nodesNews,
'#path' => $pathNews,
'#tag' => $tag
],
'pager' => [
'#type' => 'pager',
'#theme' => 'pagerer_base',
'#style' => 'standard',
'#config' => [
'display_restriction' => 0,
],
'#quantity' => 5
],
];
There is an entity in which I added the Image field. The database has a custom table with different columns, including the id of the images.
Previously, I created a batch that writes data from this table to entity fields. That is, it creates many entities from the records in the table with filled fields. I need to do the same but for images. Part of the code from the batch:
if (empty($entity_id)) {
$info = [
'type' => 'product',
'title' => $productTitle,
'field_name' => (string) $product->name,
'field_product_cid' => (string) $product->cid,
'field_custom_url' => $product->url,
'uid' => 1,
// here I need to add an image to the field_image field from the table
];
$node = $this->entityTypeManager->getStorage('node')->create($info);
$node->save();
}
else {
$storage = $this->entityTypeManager->getStorage('node');
$node = $storage->load($entity_id);
// Change fields of node.
$node->set('title', $productTitle);
$node->set('field_name', (string) $product->name);
$node->set('field_custom_url', $product->url);
// and here change field_image if the node already exists
$node->save();
}
}
Someting like this:
$node = $storage->load($entity_id);
$image_source_path = '/some/path'
$image_target_directory = 'public://some/path';
$image_data = file_get_contents ($image_source_path);
$image_alt = 'some alt text';
// Drupal 9 >= 9.3.0 or Drupal 10
$image_object = \Drupal::service('file.repository')
->writeData($$image_data, $image_target);
// Drupal 8
//$image_object = file_save_data ($image_data, $image_target);
$node->set('field_image', [
'target_id' => $image_object->id(),
'alt' => $image_alt,
]);
$node->save();
I'm trying to figure out how to pass a custom field along in a search that does not map to any column in the database.
For example I want to check if a users profile is complete or incomplete and filter on that. I want to pass along a "status" in the form post and depending on what the value is it will search on specific columns (different depending on the user type).
I have tried doing this:
$profile_status = "";
if($request->query->get("item_filter")['status']){
$profile_status = $request->query->get("item_filter")['status'];
$request->query->remove("item_filter")['status'];
}
However it appears that it removes all of my other data that comes along with the form.
This is how I built the form field in the form_filter class
$builder->add('status', ChoiceType::class, [
'expanded' => false,
'multiple' => false,
'required' => false,
'choices' => [
'Filter by Status' => '',
'Profile Complete' => 'complete',
'Profile Incomplete' => 'incomplete'
]
]);
This is how I'm querying the db:
$filterBuilder = $this->educatorUserRepository->createQueryBuilder('u');
$filterBuilder->andWhere('u.deleted = 0');
$filterBuilder->addOrderBy('u.firstName', 'ASC');
if($user->isSiteAdmin()) {
$filterBuilder->where('u.site = :site')
->setParameter('site', $user->getSite());
}
if($profile_status == 'complete'){
$filterBuilder->andWhere('u.briefBio IS NOT NULL');
} elseif($profile_status == 'incomplete') {
$filterBuilder->andWhere('u.briefBio IS NULL');
}
if ($form->isSubmitted() && $form->isValid()) {
// build the query from the given form object
$this->filterBuilder->addFilterConditions($form, $filterBuilder);
}
$filterQuery = $filterBuilder->getQuery();
Any help on this would be great.
From the method addFilterConditions in your code, I believe you are using LexikFormFilterBundle.
I never used that bundle, but you can try the following approach:
1- Use ChoiceFilterType instead of ChoiceType when defining the form fields.
2- The bundle itself offers a way to disable filtering for one field by setting 'apply_filter' => false.
3- Set the form field as mapped => false.
Your code for form definition will look like this:
$builder->add('status', ChoiceFilterType::class, [
'apply_filter' => false,
'mapped' => false,
'expanded' => false,
'multiple' => false,
'required' => false,
'choices' => [
'Filter by Status' => '',
'Profile Complete' => 'complete',
'Profile Incomplete' => 'incomplete'
]
]);
4- Drop this piece of code:
$profile_status = "";
if($request->query->get("item_filter")['status']){
$profile_status = $request->query->get("item_filter")['status'];
$request->query->remove("item_filter")['status'];
}
5- Change the code in your controller to look like bellow. This way you will be using the unmapped field status to filter by briefBio
$filterBuilder = $this->educatorUserRepository->createQueryBuilder('u');
$filterBuilder->andWhere('u.deleted = 0');
$filterBuilder->addOrderBy('u.firstName', 'ASC');
if($user->isSiteAdmin()) {
$filterBuilder->where('u.site = :site')
->setParameter('site', $user->getSite());
}
if ($form->isSubmitted() && $form->isValid()) {
if($form->get('status')->getData() == 'complete') {
$filterBuilder->andWhere('u.briefBio IS NOT NULL');
} elseif($form->get('status')->getData() == 'incomplete') {
$filterBuilder->andWhere('u.briefBio IS NULL');
}
// build the query from the given form object
$this->filterBuilder->addFilterConditions($form, $filterBuilder);
}
$filterQuery = $filterBuilder->getQuery();
I want to do this:
$builder->add('participants', EntityType::class, array(
'label' => 'Teilnehmer',
'class' => SchoolUser::class,
'multiple' => true,
'choices' => $this->getParticipantsOfEntry($builder),
'empty_value' => 'All',
'empty_data' => null,
'preferred_choices' => array(null)
));
But I get no selected 'All' - field at all. This should not be hard, I wonder where is my mistake?
'placeholder' = 'All',
did also not work for me.
How can I do this?
I know the question was 4-months ago but I ran into a similar problem so thought I would share my solution in case it helps others.
First in the Form object:
class MealFormType extends AbstractType {
public function buildForm( FormBuilderInterface $builder, array $options ) {
$builder->add( 'courses', EntityType::class, array(
'class' => MealCourse::class,
'multiple' => true,
) );
$builder->addEventListener( FormEvents::PRE_SUBMIT, function( FormEvent $event ) {
// Remove added entries here... maybe something like
$data = $event->getData();
if( ($key = array_search( 'all', $data['courses'] ) ) !== false ) {
unset( $data['courses'][$key] );
$event->setData( $data );
}
} );
}
public function configureOptions( OptionsResolver $resolver ) {
$resolver->setDefaults( ['data_class' => Meal::class] );
}
public function finishView( FormView $view, FormInterface $form, array $options) {
$newChoice = new ChoiceView( null, 'all', 'I want them all!' );
array_unshift( $view->children['courses']->vars['choices'], $newChoice );
}
}
This creates the form with multiple meal courses that can be selected, such as appetizer, soup, salad, entree, coffee, desert, ... using the buildForm() method. Also in this method an event listener for the PRE_SUBMIT event is added whose job it will be to remove any choices we have added since they are probably not real (if they were, we wouldn't have to add them in this painful way).
It sets the data_class as usual in configureOptions().
Lastly it provides the finishView() method that creates a new choice with value of 'all' that will display as 'I want them all' in the select, then adds it at the beginning for the 'choices' array for the 'courses' form entry (this would be $view->children['courses']->vars['choices'][] = $newChoice; to put it at the end).
Ok, this is fine but it really doesn't do anything more than having an additional option to click or unclick. To use this we need some Javascript to manage the form. I just stuck this Javascript in the bottom of my Twig file and use jQuery.
The Twig file:
{% block body %}
{{ form_start(mealForm) }}
{{ form_row(mealForm.courses,
{'attr': {'class': 'js-meal_course-select'}}) }}
<button type="submit">Save</button>
{{ form_end(mealForm) }}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
var mealCourseAllSelected = false;
jQuery(document).ready( function() {
// This will be called whenever an entry is selected or deselected
$('.js-meal_course-select').change( function() {
var selectedSet = $(this).val();
if( mealCourseAllSelected &&
(selectedSet.length != $(this).get(0).options.length - 1 ||
selectedSet.includes( 'all' )) ) {
var opts = $(this).get(0).options;
var len = opts.length;
for( var i = 0; i < len; ++i ) {
if( opts[i].value == 'all' ) opts[i].selected = false;
}
mealCourseAllSelected = false;
} else if( selectedSet.includes( "all" ) ||
selectedSet.length == $(this).get(0).options.length - 1 ) {
$('.js-meal_course-select option').prop( 'selected', true );
mealCourseAllSelected = true;
}
}
);
</script>
{% endblock %}
The JavaScript responds to changes to what is selected. If the select option with the value of 'all' is in the selected set, then all the select options are marked as selected, and a flag is set to say so. This is also done if everything is selected except the 'all' entry because that is kind of the definition of all.
If the 'all are selected' flag is set and there is a change, then that change must be deselecting at least one, unless that one is the select option with the value of 'all', it deselects the selection option with the value of 'all' and clears the 'all are selected' flag. If all are selected and the user tries to unselect just the select option with the value of 'all', it is just reselected.
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