I'm wondering how to get the current page name, basically 'just' the last parameter in the route (i.e. /news or /about). I'm doing this because I want to be able to have the current page in the navigation highlighted.
Ideally, I'd like to store the current page name in a global variable so that in Twig I can just compare the current page name against the link and add a class accordingly.
I can't figure out how to add the current page name to a global variable though. I've tried using something like this:
$app['twig']->addGlobal('current_page_name', $app['request']->getRequestUri());
at the top of my app.php file, but an 'outside of request scope' error. But I wouldn't like to have to include this in every route.
What's the best way to do this?
If you put it into an app-level before middleware like this, that'll work:
$app->before(function (Request $request) use ($app) {
$app['twig']->addGlobal('current_page_name', $request->getRequestUri());
});
The "page name" part of your question is unclear, are you looking for the current route's name? You can access that via $request->get("_route") even in the before middleware, as it gets called when routing is already done.
You could also generate navigation list directly in stand alone nav twig template. And then import it in to the main template. Then you would only have to get silex to pass to the view the current page identifier. Simplest way... for example from Silex you would have to pass in the "path" variable to your view. Probably it would more convenient to to fetch nav_list from database and pass it in to twig template as global array variable instead. However this example is the simplest you could get to do what you intend.
nav.twig
{% set nav_list = [
["./", "home"],
["./contact", "contact"],
["./about", "about us"]
{# ... #}
] %}
{% set link_active = path|default("") %}
{% for link in nav_list %}
<li><a href="{{ link[0] }}" class="{% if link[0] == link_active %} activeClass {% endif %}" >{{ link[1] }}</a></li>
{% endfor %}
app.php
//...
$app->match('/about', function (Request $request) use ($app) {
return $app['twig']->render('about.twig', array(
'path' => './'.end(explode('/', $request->getRequestUri()))
));
});
//...
Related
Is there a standard/preferred way in Symfony to pass common variables to the base template?
There are things that are on every page, like a username in the menu that I obviously don't want to have to remember to add for every controller.
My thought was to create a controller for the template and return the data. But wondering if there is something more built in to handle this.
return $this->render(
'#secured/account/profile.html.twig',
array('userForm' => $form->createView(),
'base' => call_base_layout_controller()
);
{# templates/account/profile.html.twig #}
{% extends '#secured/base.html.twig' %}
{% block body %}
{% endblock %}
I cannot find it in the current version docs but, as far as I know you can still access a lot directly from twig through the app twig variable defined automatically by the framework for every request https://symfony.com/doc/3.4/templating/app_variable.html
For example, you can get the current user's username as follows:
{{ app.user.username }}
app holds user, request, session, environment, and debug so you can put other values needed into the session or environment variables, and fetch/render those values directly in the template.
If the data that you want is related to the authenticated user, I would recommend what Arleigh Hix said
Otherwise you can create a class that extends AbstractExtension and fill it with your logic
Anything on this class can be accessed from all your twig pages
// src/Twig/AppExtension.php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class AppExtension extends AbstractExtension
{
public function getFunctions()
{
return [
new TwigFunction('myGlobalData', [$this, 'myGlobalData']),
];
}
public function myGlobalData()
{
return "what ever you want ( ex: a query resualt..)";
}
}
Than on your twig template just call it like this
{{ myGlobalData() }}
Even if it retuns an array, u can access it.
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.
I am running Symfony 4 app with Vue.js enabled. Is there any good practice to send my data from Twig templates to Vue.js components? Currently I have a number of data items for example on my header component, and HTML element data section looks like this:
<header-nav class="headnav headnav-fixed z-depth-1"
logo="{{ asset('build/images/site_logo.png') }}"
username="{{ app.user.name }}"
logout-url="{{ path('logout') }}"
logout-title="{% trans %} Logout {% endtrans %}"
instruction-url="{{ path('system_instruction_download') }}"
instruction-title="{% trans %} Download instruction {% endtrans %}"
current-locale="{{ app.request.getLocale()|upper }}"
v-bind:locales="{{ locales|json_encode }}"
>
Let's say I have a number of different URL's and other stuff. What is the best way to send the data? Should I first prepare an array of URL's on my controller? Which controller should it be if I want to prepare some global variables which will be used on my header, so they shouldn't be locked only on one controller.
Assuming that you render multiple "vue applications", you can define global variables with
1) twig configuration
Documentation says:
"Twig allows to inject automatically one or more variables into all templates.
These global variables are defined in the twig.globals option inside the main Twig configuration file"
2) You could create abstract controller with function merging variables
// MyAbstractController.php
protected function getTwigVariables(array $vars) {
$globals = [];
// ... fill $globals with your stuff
return array_merge(['globalVar1' => ], $vars);
}
// TestController extends MyAbstractController
public function indexAction() {
//...
return $this->render('viewPath.html.twig', $this->getTwigVariables([
'specificVariable' => 'variableContent'
]));
}
You could also embed controllers inside your main twig.
You can create headerAction, footerAction etc. and create subrequest for this actions.
For storing variables you can also use script tags
// twig
<script id="serverJson" type="application/json">{{ jsonContent|json_encode()|raw }}</script>
// serverJson.js
const configElement = document.getElementById("serverJson");
export default JSON.parse(configElement.innerHTML);
// ViewTemplate.vue
import serverJson from "path-to-serverJson.js"
I have a Project entity, which has a controller defining many routes:
projects/1
projects/1/foo
projects/1/bar
I need a service to provide the current project. The use case is that I have dependencies in my base twig templates which need to know the current project. i.e. a dropdown project selector that is outside the context of the template the current controller is serving.
I've tried creating a service getting route info with $container->get('router.request_context');, however, that only provides a path. I don't want to have to parse the path string if I don't have to.
What is the most proper approach?
If I understood you correctly the solution for your problem is rendering/embedding controller. Of course it's simplest, yet somehow acceptable solution for rendering parts of html with some custom logic apart from current template.
You can read about rendering/embedding controllers.
Some snippets...
Define controller:action (obviously the logic in my example is pretty straight forward):
/**
* Generate select input field.
*
* #Route("/widget", name="project_widget")
* #Method("GET")
*/
public function widgetAction()
{
$repo = $this->getDoctrine()
->getManager()
->getRepository('AppBundle:Project');
// #NOTICE: Wee need master request info, because our widget
// is rendered and called as subrequest.
$masterRequest = $this->get('request_stack')->getMasterRequest();
// #NOTICE: You need second parameter as default value in case there is no id in masterRequest route.
$currentProjectId = $masterRequest->get('id', 0);
$currentProject = $repo->find($currentProjectId);
$projects = $repo->findAll();
return $this->render('project/widget.html.twig', [
'currentProject' => $currentProject,
'projects' => $projects,
]);
}
Then you need to create the template project/widget.html.twig for it:
<div class="widget_project_selection">
{% if projects is not empty %}
<select name="widget_project_selection" id="widget_project_selection">
<option value="">None</option>
{% for project in projects %}
<option value="{{ project.id }}"
{# #NOTICE: If this is current project, select it #}
{{- currentProject and project.id == currentProject.id
? 'selected="selected"'
: '' -}}
>
{{- project.title -}}
</option>
{% endfor %}
</select>
{% else %}
<span>{{ 'Sadly, no projects yet :('|trans }}</span>
{% endif %}
</div>
and at last (but not least) render it somewhere like in base.html.twig:
{{- render(controller('AppBundle:Project:widget')) -}}
I've created for you a Github repo as reference. It's a small Symfony app with similar setup. You can even run it if you like, don't forget about dependencies and database update thou. Entry point is /app_dev.php/project/
Take a look at widgetAction, widget template and example usage in base.html.twig.
EDIT: But that's not everything. You said you need a service. If for some reason rendering/embedding controller is not an option for you or you really whant to use a Service (as in Dependency Container) you can extend Twig and use the full power of services.
I've also implemented a Twig Filter as example to show you the real power of Twig Extensions in here and here (usage in templates).
Check out Twig Extension and Extending Twig for more info about Twig Extensions.
Also check out service.yml for service and extension definitions - if you are not using Symfony3.3+, there will be some additional work to do - defining service and extension directly.
In your controller, you can use type hinting to load the "current" entity via the route.
For example:
#myrouter.yml
current_project:
path: /projects/{project}
Separately, your controller...
//mycontroller.php
public function myControllerAction(Request $request, Project $project)
{
//$project is entity (assuming valid) loaded via the route above
return $this->render('mytemplate.twig', ['project' => $project]);
}
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.