Grouped view for datasets in sonata admin list view - symfony

I am wondering if there is a easy way to implement a group view for datasets grouped by its "root" element.
Lets say i have a "person" entity. Many persons can have a "root" person they belong to. So i would add a reference to the person entity itself.
Now i don't want to show every person in the flat list view. Instead i want to display only every root-person and with clicking this dataset a accordion opens with the subordinated person entities ... how is that possible?
It would also be fine without a accordion, it can be enough if the subordinated entities are indented a bit ...
Can somebody give me a clue which approach i should follow? I would be the if i can reuse the most of the sonata admin functionality, especially the templates ...
Thanks

Was solving something similar in past. Got that working in few steps:
Override first field in your Datagrid
With this code you say that you want use my_custom_template.html.twig to render this field (in your admin class).
protected function configureListFields(ListMapper $list)
{
$list->add('yourFirstField', null, ['template' => 'my_custom_template.html.twig'])
}
Enable filtering for yourFirstField
Prepare filtering for your parent field (in your admin class)
protected function configureDatagridFilters(DatagridMapper $filter)
{
$filter->add('parent');
}
Write custom template for first field
Then in your custom template you use to set up filter value on click on link.
{% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}
{% block field %}
{% if object.isRoot %}
{{ object.name }}
{% else %}
{{ object.name }}
{% endif %}
{% endblock %}
Later on you can override break breadcrumbs builder service (https://sonata-project.org/bundles/admin/master/doc/reference/breadcrumbs.html) and create nice path in breadcrumbs, so user can go navigate.

Related

Symfony twig controller route

I am begginer and I have little problem.
I have:
I have one primary menu with links to Controller main actions (index) and one secondary menu with links to other actions like create / edit / delete.
My problem is:
I don't know how to tell twig, that links in primary menu are active when any action in that controller is called.
In secondary menu I have:
{% set route_name = app.request.attributes.get('_route') %}
<a class="{% if route_name == item.route %} active {% endif %}">Link</a>
I have tried:
I tried to do it with prefixes on Controller, but it didn't worked.
Any hints or tips please? Thank you!
If your menu has only 2 nested levels, you can set a #Route annotation in the whole controller:
/**
* #Route(path="admin/pegass", name="admin_pegass_")
*/
class PegassController extends BaseController
{
For every action in your controller, you just need to set the end of the name and path:
/**
* #Route(name="list_users", path="/list-users")
*/
public function userList()
{
Finally, when rendering the first level of your menu, you can use:
{% set route_name = app.request.attributes.get('_route') %}
<a class="{% if route_name[0:13] == 'admin_pegass_' %} active {% endif %}">Link</a>
For more complex menus, breadcrumbs are not trivial, it will be easier to create an object representation of it in order to traverse it and perform operations in a more generic way (you can use a bundle like KnpMenu).

How to add a static content in a specific drupal page something like like using {% if is_front %} {% endif %} to add it in the home page

I am new to drupal and I tried to add a simple image as static in the html.html.twig page.
To display this image in the home page only I added this condition
{% if is_front %}
{% endif %}
What is the condition to add it in another specific page example /contact/
I tried this code inside the page but it does not work, should I wrap it with some codes?
if(drupal_valid_path('contacts') == 1) //this exists
{
print_r('Exists!');
}
Please help! Many thanks.
Your question is mixing twig with a drupal 7 function. I suppose anyway you're on D8/D9.
I also suppose the "contacts" page is a node. If it is somethings else - e.g. a view page - instead of checking for the node, you may have to check the route.
There are various approach, two possible would be:
A hook preprocess where you load the node if exists
function hook_preprocess_html(&$variables) {
$node = \Drupal::routeMatch()->getParameter('node');
if ($node instanceof \Drupal\node\NodeInterface) {
$variables['node'] = $node;
}
}
And the in the twig check the id of the node is the right one
override the html.html.twig for the specific node, e.g. html--node--10.html.twig. In this case I'd suggest not to duplicate all the content of the base html.html.twig, but to use the embed tag to override only the appropriate block.

Adding own action to SonataAdminBundle dropdown menu

We use the SonataAdminBundle with our Symfony2 application. When editing an entity I want to add an own action to the dropdown menu which is located in the top right corner, but I have no idea how this works.
I know I can add own routes via configureRoutes(RouteCollection $collection) and how to add batch actions or add own actions behind entities in the list view, but how can I add an own link in the actions dropdown in the edit view?
It is basically just a link like "Show me this entity in the frontend", so no big logic is needed.
One way would be to override the template that is used when editing. Now, what you need to do is:
Create new directory (if you already haven't) in app/Resources called SonataAdminBundle. Inside, create another one called views. This would create a path like app/Resources/SonataAdminBundle/views. This is Symfony basic template overriding. You can read more on that subject here.
Now, you should copy the original template following the same path as it is, inside the original bundle. The template file we are interested here is located in sonata-project/admin-bundle/Resources/views/CRUD/base_edit.html.twig. This means that you have to create another folder inside views (the one we just created in app, called CRUD. So, now we have to following path app/Resources/SonataAdminBundle/views/CRUD. Paste the template (base_edit.html.twig) inside and we can start editing.
Keep in mind that the following template is used in every edit action you have. So it's up to you whether you want to display that link in every edit_action or not. I will show you 1 way to limit that for specific action.
The block you gonna edit is {% block actions %} which is responsible for rendering the dropdown. This is how it should look now:
{% block actions %}
<li>{% include 'SonataAdminBundle:Button:show_button.html.twig' %}</li>
<li>{% include 'SonataAdminBundle:Button:history_button.html.twig' %}</li>
<li>{% include 'SonataAdminBundle:Button:acl_button.html.twig' %}</li>
<li>{% include 'SonataAdminBundle:Button:list_button.html.twig' %}</li>
<li>{% include 'SonataAdminBundle:Button:create_button.html.twig' %}</li>
{% endblock %}
Now all that's left to do is insert your link after last <li> tag.
{% if admin.id(object) is not null and app.request.get('_route') == 'my_route' %}
<li>
View in Frontend
</li>
{% endif %}
admin.id(object) will return the current ID of the item you edit. app.request.get('_route') will return the route of your edit action. You can remove that if you want your link to be displayed in all edit actions. Change View in Frontend with your route name using admin.id(object) and you should be good to go.
In your admin class, override the following method:
public function getActionButtons($action, $object = null)
{
$list = parent::getActionButtons($action, $object);
$list['upload'] = [
'template' => ':admin:my_upload_button.html.twig',
];
return $list;
}
This will add a custom action button on all the pages of this admin. You can add any logic in here to decide which pages ($action-s) you want to display the button on.
You can do what you want in the template, but just to complete my example and show the connection with my custom action:
<li>
<a class="sonata-action-element" href="{{ admin.generateUrl('upload') }}">
<i class="fa fa-cloud-upload" aria-hidden="true"></i>
Upload stuff
</a>
</li>
Another way would be to override the method generateObjectUrl() in your object's admin class.
/**
* #see \Sonata\AdminBundle\Admin\Admin::generateObjectUrl()
*/
public function generateObjectUrl($name, $object, array $parameters = array(), $absolute = false)
{
if ('show' == $name) {
return $this->getRouteGenerator()->generate('your_route_to_public_facing_view', [
'id' => $this->getUrlsafeIdentifier($object),
], $absolute );
}
$parameters['id'] = $this->getUrlsafeIdentifier($object);
return $this->generateUrl($name, $parameters, $absolute);
}
And that's it. No mucking with templates. And no template code that will run on every other admin.
To get the link to appear automatically, you will have to add something to the $showMapper via configureShowFields(). (If anyone knows a better way, please do tell.)
Overriding generateObjectUrl() has another bonus: If you display a show button on the $listMapper, the URL there will be updated there as well.
Edited to say: since this overrides the show route, you will no longer be able to use that built-in feature. That's ok for me since I need to view my object with all the front-end css and js loaded.

Using repository class methods inside twig

In a Symfony2.1 project, how to call custom entity functions inside template? To elaborate, think the following scenario; there are two entities with Many-To-Many relation: User and Category.
Doctrine2 have generated such methods:
$user->getCategories();
$category->getUsers();
Therefore, I can use these in twig such as:
{% for category in categories %}
<h2>{{ category.name }}</h2>
{% for user in category.users %}
{{ user.name }}
{% endfor %}
{% endfor %}
But how can I get users with custom functions? For example, I want to list users with some options and sorted by date like this:
{% for user in category.verifiedUsersSortedByDate %}
I wrote custom function for this inside UserRepository.php class and tried to add it into Category.php class to make it work. However I got the following error:
An exception has been thrown during the rendering of
a template ("Warning: Missing argument 1 for
Doctrine\ORM\EntityRepository::__construct(),
It's much cleaner to retrieve your verifiedUsersSortedByDate within the controller directly and then pass it to your template.
//...
$verifiedUsersSortedByDate = $this->getYourManager()->getVerifiedUsersSortedByDate();
return $this->render(
'Xxx:xxx.xxx.html.twig',
array('verifiedUsersSortedByDate' => $verifiedUsersSortedByDate)
);
You should be very carefull not to do extra work in your entities. As quoted in the doc, "An entity is a basic class that holds the data". Keep the work in your entities as basic as possible and apply all the "logic" within the entityManagers.
If you don't want get lost in your code, it's best to follow this kind of format, in order (from Entity to Template)
1 - Entity. (Holds the data)
2 - Entity Repositories. (Retrieve data from database, queries, etc...)
3 - Entity Managers (Perform crucial operations where you can use some functions from your repositories as well as other services.....All the logic is in here! So that's how we can judge if an application id good or not)
4 - Controller(takes request, return responses by most of the time rendering a template)
5 - Template (render only!)
You need to get the users inside your controller via repository
$em = $this->getDoctrine()->getEntityManager();
$verifiedusers = $em->getRepository('MYBundle:User')->getVerifiedUsers();
return array(
'verifiedusers' => $verifiedusers,
);
}

writing doctrine query with symfony 2

i have two tables:
products products_images
-id -id
-name -product_id
-image_name
There is one to many relation from product to products_images table
I have created two entities with its relation defined as: Product and ProductImage
I need to get list of products and its images but the limiting the record of no of images to 1.
Currently, I did this :
$product = $this->getDoctrine()
->getRepository('TestBundle:Product');
Then in the twig template:
{% for image in product.images if loop.first %}
{% endfor %}
I used this loop to fetch one image for that product. I dont think this is efficient way to do since i have fetching all the images for that product.
What I want to do is just fetch only one image per product from the database? How can i do this ?
I'd add another method to the Product-entity, something like getThumbnail. This encapsulates the logic of which image is considered the thumbnail (i.e your view does not contain any logic of whether to display the first, last or any other product image, it just asks the product for its thumbnail):
in the view
{% if product.thumbnail %}
{{ // Output the product.thumbnail-entity }}
{% endif %}
In the entity
public function getThumbnail ()
{
return $this->getImages()->first();
}
Now, after profiling, if you're concerned that the full image collection is loaded when only a single image/product is shown, then I'd update the repository DQL to only fetch-join a single image, or make sure that the association to the images is EXTRA_LAZY

Resources