I'm operating on a symfony application and I would like to set a new variable from all controllers to all rendering.
The reason is that something in my footer has become dynamic and in compliance with the MVC pattern I'd like to put the processing of this new data in my controllers.
What is the good way to do this with symfony ?
EDIT
I am not using symfony as a REST API, the Symfony server is only serving twig rendered templates as HTML.
Details on my case :
the current twig template has hard-coded titles for a form :
<div>
<h2>Today</h2>
<!-- today's inputs .... -->
</div>
<div>
<h2>Tomorrow</h2>
<!-- tomorrow's inputs .... -->
</div>
I'd like to give to variables to my views : $today and $tomorrow.
This way I'd be able to render day names instead of today or tomorrow.
<div>
<h2>{{ today }}</h2>
<!-- today's inputs .... -->
</div>
<div>
<h2>{{ tomorrow }}</h2>
<!-- tomorrow's inputs .... -->
</div>
For example if today is Tuesday, variables has to be assigned this way :
$today = "Tuesday" and $tomorrow = "Wednesday".
What's more
This is not a question about this specific case. I'd like to now if there is a way to pass a variable to all views without editing all controllers. As I see it, I'd put a parent action to all controller to generate this variable. I just wanted to know if this is the usual way.
I don't want to use ajax calls, and I don't want to put complex twig code inside my template. I want to handle this via controllers.
Please read official documentation about using global variables.
Off the top of my head - you can inject...
...scalar values from the global twig config
...scalar values from the service container parameters
...services (read php objects)
Or you can write Twig extension, like:
class DateExtension extends \Twig_Extension
{
public function getFunctions()
{
return [
new \Twig_SimpleFunction('get_date', array($this, 'getDate'))
];
}
public function getDate($date)
{
// format it how you want
return (new \DateTime($date))->format('Y-m-d H:i:s');
}
}
And then use it in any template simply by:
<div>
<h2>{{ get_date('today') }}</h2>
<!-- today's inputs .... -->
</div>
<div>
<h2>{{ get_date('tomorrow') }}</h2>
<!-- tomorrow's inputs .... -->
</div>
About what kind of "dynamic" do you speak? If this dynamic goes from changing of some value stored in the data storage of your app, than, I belive, you could define controller action to retrive this data from, for example, your database and then call it via AJAX on every page load to obtain those value on client-side. Or maybe even use help of WebSocket's. But this is just assumption. If you really need help you should provide us more information about context of your task.
Related
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.
I'm trying to get the following behavior for a certain template:
<body onload="someInitFunction();">
Let's say i have the following markup (i'm using mrt router, not iron-router, for {{renderPage}}):
// Main Template
<head>
<title>meteorite-knowviz</title>
</head>
<body>
{{> header}}
<div class="container-fluid">
<div class="row">
{{renderPage}}
</div>
</div>
{{> footer}}
</body>
That renderPage is the secondTemplate:
<template name="secondTemplate">
{{#if currentUser}}
<div class="col-md-2">
<div class="list-group">
<a class="list-group-item" href="{{render thirdTemplate please...}}">Third Template</a>
<a class="list-group-item" href="{{render fourthTemplate please...}}">Fourth Template</a>
</div>
</div>
// In this case let's say thirdTemplate gets rendered
{{render the choice taken above please...}}
{{/if}}
</template>
And within this template, depending on which link was clicked on, (in this case the third) there will finally be a thirdTemplate, which will show a data visualization with some help by a javascript framework, which will be in need of a <body onload="initFunction();">in order to display the data:
<template name="thirdTemplate">
<div class="col-md-5">
<h2>THIS!! section needs a "<body onload="initFunction();"> in order to work" ></h2>
</div>
<div class="col-sm-5">
<h2>Some other related content here</h2>
</div>
</template>
To sum up i have three questions:
1a. How could i get the third template to get a <body onload="initFunction();">
2a. In which way can i render different templates within the secondTemplate?
2b. Can i use a {{renderPage}} within this template even though this template is the renderedPage in the main template or should i do it in some other way?
In order to get the <body onload="initFunction();"> i had to do the following:
First add the following function to a .js file in the client folder:
Template.thirdTemplate.rendered = function() { // Template.thirdTemplate.created - also worked.
$('body').attr({
onload: 'init();'
});
}
This however got me an error saying that initFunction is not defined. In an standard html page my could work just fine, but in meteor i had to change my function from:
function initFunction(){
//what ever i wished to do
}
To:
init = function() {
//what ever i wished to do
}
Regarding the rendering of pages, iron-routing is the way to go since the router add on is not under development any more.
1a. How could i get the third template to get a <body
onload="initFunction();">
You probably want to call initFunction when the third template has been rendered, so just put your call to it in the rendered callback.
Template['thirsTemplate'].rendered = function(){
initFunction()
}
2a. In which way can i render different templates within the
secondTemplate?
2b. Can i use a {{renderPage}} within this template even though this
template is the renderedPage in the main template or should i do it in
some other way?
Listen for clicks on the links, and when one happen you manually render the desired template (possible with Meteor.render, if you need reactivity) and add it to the right node in the document. See this question.
It may be possibly to achieve with router (I don't know that package).
I think that what you want to use is the created callback, that will be called each time your template is created, and not the rendered callback, that would be called each time a change has caused the template to re-render.
Template.thirdTemplate.created = function(){
initFunction()
}
See the documentation for templates for other types of callbacks: http://docs.meteor.com/#templates_api
I am using Kendo UI as the frontend library for a Symfony2.3.4 app and trying to establish best practices for working with Controllers and the Twig templating engine. My question is specific to the Kendo PHP wrapper library which allow for easy instantiation of Kendo objects.
The problem is passing those objects to twig templates. For example:
// ItemController.php
public function addAction(){
$options = ['option1', 'option2', 'option3']
$dropdown = new \\Kendo\UI\DropDownList($options);
$select = $dropdown->render();
return $this->render('TestBundle:Titles:add.html.twig',
['options' = $options, 'dropdown' => $dropdown, 'select' => $select];
}
With this controller I can pass the Kendo object ($dropdown) into the template, but the actual rendered dropdown ($select) throws an array to string error. Now I can still render the object in the template by using the $options array:
// add.html.twig
<h1>Add Item</h1>
<!-- This renders the Kendo object -->
{{ dump(dropdown) }} <!-- object(Kendo\UI\DropDownList) (5) { etc -->
<!-- This throws an error -->
{{ dump(select) }}
<input id="dropdownlist" />
<script>
// this renders the dropdown
$("#dropdownlist").kendoDropDownList({
dataSource: {
data: [
{% for option in options %}
"{{ option }}",
{% endfor %}
]
}
});
</script>
There doesn't seem to be an efficient way of passing a string snippet of html into a template in the Symfony2/Twig system. I can still render the dropdown in the template (by using the $options array) but then what's the use of having the PHP Kendo object?
Also, say I have an owner for each item and I want to create a dropdown of owner options in the addAction of the ItemController? Can I have a selectAction in the OwnerController that returns html for an Owner dropdown or an array of Owners?
This type of layout seemed to have been dealt with by the slots of Symfony 1.* ... but it is not clear how to do it with Symfony2 and Twig.
What is the 'best practice' way of dealing with formbuilding in Symfony ... particularly when you are calling from more than Entity (Items & Owners) in one form?
And/or is there a way of properly using PHP wrappers for JS objects like those in Kendo UI within a standard Symfony2 controller?
I would like to know if there is a method to pass some variables (text from textarea) from Twig to Symfony2 controller via form.
<form action="{{ path('admin_core_save') }}" method="post">
<div id="edit-template">
{% if template.getData() is defined %}
<textarea id="template">{{ template.getData() }}</textarea>
{% endif %}
</div>
<input type="submit" value="Save" />
</form>
When I press save button it is going to saveAction() method
public function saveAction(Request $request)
{
var_dump($request);
return new Response('abc');
}
but response does not contain any textarea text. Is there a way to get this there?
I know I can build form inside the controller and send it to Twig, but I would like to know if this way is possible.
you can access POST values through request object like:
$this->get('request')->request->get('name');
I'm sure you have to learn a bit about Symfony2 Form Component. You will find that symfony already has built-in system for rendering forms handling user data posted through them.
Answering your question. There is a Request object that provides you full access to all request data, including POST variables.
To access POST values use Request::get() method:
$request->get('variable_name');
To pass any data to the twig template, use TwigEngine::renderResponse
$this->container->get('templating')->renderResponse('AcmeDemoBundle:Demo:template.twig,html',
array(
'someVar' => 'some value'
)
);
This var will be avaliable in your template as:
{{ someVar }}
I have a table, using display tag, in my application, that is using spring web flow. I would like to have a check box in each row, a button that allows me to select/uselect all and a button to execute a function. After clicking the button, the action will perform some database actions and the page should be render, so we can see these changes.
I don´t know which could be the best option, submitting the whole table
<form method="POST" (more params)>
<display:table id="row">
....
</display:table>
</form>
Or only the checkbox column. I this case I wouldn´t know how to implement it.
I have tryed two different approaches:
1. Using a simple input text, checkbox type. This is not possible, because when I submit the form, I need to set a path to another page.jsp (I am working with flows). Besides, I wouldn´t know how to send these values to java backend.
Using spring tags.
In this case, the problem comes whith the class conversationAction
I found some examples, but allways using MVC and controller cases.
How could I implement this issue??
EDIT
I have found a kind of solution, but I faced a new problem...
flow.xml
var name="model1" class="com.project.Model1"/>
var name="model2" class="com.project.Model2"/>
view-state id="overview" model="formAggregation">
...
</view-state>
page.jsp
form:form modelAttribute="formAggregation.model1" id="overviewForm">
...
/form:form>
...
form:form method="POST" modelAttribute="formAggregation.model2">
display:table id="row" name="displayTagValueList" requestURI="overview?_eventId=tableAction">
display:column title="">
form:checkbox path="conversationIds" value="${row.threadId}"/>
/display:column>
/display:table>
input type="submit" name="_eventId_oneFunction" value="Send>>"/>
/form:form>
FormAggregation.java
#Component("formAggregation")
public class FormAggregation {
private Model1 model1;
private Model2 model2;
//Getters and setters
I need this aggregator, because I need both models. I have tested it one by one and it is working as wished. Any idea about that??
Thanks!!
I couldn´t find a solution to add two model in a view-state. So I made a workaround, adding the fields I needed to the model I was using, com.project.Model1. So the result is:
page.jsp
<form:form method="POST" id="tableForm" modelAttribute="model1">
<display:table id="row">
<display:column title="">
<form:checkbox path="chosenIds" value="${row.id}"/>
</display:column>
<display:footer>
<div class="tableFooter" >
<input type="submit" name="_eventId_workIds" value="Send"/>
</div>
</display:footer>
</display:table>
</form:form>
flow.xml
<var name="model1" class="com.project.Model1"/>
...
<transition on="workIds" to="overview" validate="false">
<evaluate expression="actionBean.workIds(model1.chosenIds)" />
</transition>
java class
public void workIds(List<Long> ids) {
Hope it helps