How to load two templates inside controller function? - symfony

I have a list.html.twig file in which I have included another template file like:
<div class="panel-body">
{{ include('default/partials/groupSettings.html.twig') }}
</div>
And in my controller function following is given:
public function settingsListAction()
{
$settingsGroup = $this->getDoctrine()->getRepository('AppBundle:J1SettingGroup')->findAll();
return $this->render('default/settingsList.html.twig', array('settingsGroup' => $settingsGroup));
$settingsList = $this->getDoctrine()->getRepository('AppBundle:J1Setting')->findAll();
return $this->render('default/partials/groupSettings.html.twig', array('settingsList' => $settingsList));
}
But it only loading the first template and not the second.

includes should be written like so :
{{ include('[BundleName]:[directory_with_your_template]:templatename.html.twig', { 'settingsGroup': settingsGroup }) }}
and in your controller you render only the parent template and pass the params you need to the child template
public function settingsListAction()
{
$settingsGroup = $this->getDoctrine()->getRepository('AppBundle:J1SettingGroup')->findAll();
$settingsList = $this->getDoctrine()->getRepository('AppBundle:J1Setting')->findAll();
return $this->render('default/settingsList.html.twig', array('settingsGroup' => $settingsGroup, 'settingsList' => $settingsList));
}

Related

Symfony 5 How to display multiple Google Charts with jQuery ui Tabs

My first Symfony 5 attempt and I am trying to use Google Charts for the first time. I am using CMENGoogleChartsBundle which provides a Twig extension and PHP objects to display the Charts.
I want to display different Charts which should be accessible through jQuery UI Tabs I also have a table with all the data being displayed. When now clicking on a Tab with a country for example I want the Graph and table updated. I tried to display all the data directly in my twig template (fruitsoverview.html.twig), but when clicking on a tab I would get the page rendered again just below the tabs, plus I would lose any possible set up of my search filter, if the whole page would be re-loaded. I then read that you can create a view just with the content which needs updating so I have done that and in my controller I now have:
if ($request->isXmlHttpRequest())
{
return $this->render('fruits/chart.html.twig', [
'searchFilter' => $searchFilter->createView(),
'fruitCounts' => $fruitCounts,
'barchart' => $barchart
]);
}else{
return $this->render('fruits/fruitsoverview.html.twig', [
'searchFilter' => $searchFilter->createView(),
'fruitCounts' => $fruitCounts,
'barchart' => $barchart
]);
}}
This solved the problem with the rendering, but the chart is not shown. The chart data is available in the twig view but the Graph only gets shown on the default Tab. I do get the table displayed with the correct data. What could be wrong or better question, how is it supposed to be set up correctly. I am sure I have not understood the concept of Symfony yet and probably not of the jQuery ui Tabs. Any help would be highly appreciated. Thank you very much in advance.
UPDATE
I have now updated my code and got partly working besides the barchart. So the twig variables for the table are updating with the new content after the AJAX request, but the barchart does not change. I have done
{{ dump(barchart) }} in the chart.html.twig and I do get the updated data inside of that as well, but the chart is not re-drawn. How can I achieve that?
controller
if ($request->isXmlHttpRequest())
{
$response = new JsonResponse();
$response->setStatusCode(200);
return new JsonResponse([
'html' => $this->renderView('fruits/chart.html.twig', [
'fruitCounts' => $fruitCounts,
'barchart' => $barchart
])
]);
In my main template I have the below code and Javascript. $("div#client-loop-container").html(data.html); seems to update the twig variables in my chart.twig template.
<div id="client-loop-container">
{% include 'chart.html.twig' %}
</div>
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
<script type="text/javascript">
$( function() {
$('#country-tabs a').on('click', function (e) {
e.preventDefault()
$(this).tab('show');
var $this = $(this),
loadurl = $this.attr('href');
var form = $('form');
var jsonData = $.ajax({
url: loadurl,
type: 'POST',
data: form.serializeArray(),
dataType: 'json',
success: function(data, status) {
$("div#client-loop-container").html(data.html);
}
}).responseText;
});
chart.html.twig
<div id="client-loop-container">
<div class="w-100" id="div_chart"></div>
<table class="table">
<tr>
<th>Date</th>
{% for fruitcount in fruitcounts %}
<th>{{ fruitcount }} </th>
{% endfor %}
</tr>
</table>
</div>
{% block javascripts %}
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
{{ gc_draw(barchart, 'div_chart') }}
</script>
{% endblock %}
Here's a possible solution. The code below uses a single <div> for displaying charts but you might be able to adapt to a tab & id="..." solution. The approach uses jQuery.getScript() to run chart-specific javascript. A ChartService uses the cmen/google-charts-bundle to generate the javascript. The script is supplied to the displaying template by a Controller response. There are six different charts that can be displayed. The page displays charts in pseudo-carousel style.
You might ask why go to all this trouble when it's possible to draw all six charts once and use javascript to hide and show one chart at a time. The answer is that charts 2 - n will usually not have all of its options displayed. I developed this solution so that the vertical axis would always show values at grid lines.
ChartService (with chart definitions eliminated for brevity):
namespace App\Service;
use App\Entity\RV;
use App\Entity\Summary;
use CMEN\GoogleChartsBundle\GoogleCharts\Options\VAxis;
use CMEN\GoogleChartsBundle\GoogleCharts\Charts\Histogram;
use CMEN\GoogleChartsBundle\GoogleCharts\Charts\LineChart;
use CMEN\GoogleChartsBundle\Twig\GoogleChartsExtension;
use Doctrine\ORM\EntityManagerInterface;
class ChartService
{
private $em;
private $gce;
public function __construct(EntityManagerInterface $em, GoogleChartsExtension $gce)
{
$this->em = $em;
$this->gce = $gce;
}
public function buildChart($chartType, $class, $subtype = null)
{
switch ($chartType) {
case 'line':
return $this->lineChart($class, $subtype);
break;
case 'histogram':
return $this->histoChart($class);
default:
break;
}
}
private function lineChart($class, $type)
{
...
return $chart;
}
private function histoChart($class)
{
...
return $histo;
}
public function getChartJs($chartSpecs, $location)
{
$chartType = $chartSpecs['type'] ?? null;
$class = $chartSpecs['class'] ?? null;
$subtype = $chartSpecs['subtype'] ?? null;
$chart = $this->buildChart($chartType, $class, $subtype);
$js = $this->getStart($chart, $location);
$end = $this->getEnd($chart, $location);
$part3 = substr($end, 0, strlen($end) - 1);
return $js . $end;
}
private function getStart($chart, $location)
{
return $this->gce->gcStart($chart, $location);
}
private function getEnd($chart, $location)
{
return $this->gce->gcEnd($chart, $location);
}
}
chartSwitch.js
$(document).ready(function () {
var i = $('#currentChart').attr('data-chart');
chartSwitch(i);
$('#chartNext').on('click', function () {
i++;
if (6 === i) {
i = 0;
}
chartSwitch(i);
});
$('#chartPrevious').on('click', function () {
i--;
if (-1 === i) {
i = 5;
}
chartSwitch(i);
});
function chartSwitch(i) {
$('#currentChart').attr('data-chart', i);
var url = "/js/" + i;
$.getScript(url);
}
});
Controller method for /js/ + i
/**
* #Route("/js/{which}", name="js")
*/
public function returnChartJs(ChartService $chart, $which)
{
$available = [
['type' => 'line', 'class' => 'C', 'subtype' => 'Price'],
['type' => 'line', 'class' => 'C', 'subtype' => 'Count'],
['type' => 'line', 'class' => 'B+', 'subtype' => 'Price'],
['type' => 'line', 'class' => 'B+', 'subtype' => 'Count'],
['type' => 'histogram', 'class' => 'C'],
['type' => 'histogram', 'class' => 'B+'],
];
$js = $chart->getChartJs($available[$which], 'chartA');
$response = new Response($js);
return $response;
}
Relevant pieces from displaying template:
<div class="col-9 text-center">
<div class="row">
<div class="col-3"></div>
<div class="col-3">
<span id="currentChart" data-chart="0"></span>
<nav aria-label="Page navigation">
<ul class="pagination">
<li class="page-item">
<a id="chartPrevious" class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">« Chart</span>
<span class="sr-only">Previous</span>
</a>
</li>
<li class="page-item">
<a id="chartNext" class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">Chart »</span>
<span class="sr-only">Next</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
<div class="row">
<div class="col-12">
<div id="chartA"></div>
</div>
</div>
</div>
...
{% block javascripts %}
{{ parent() }}
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
{{ encore_entry_script_tags('charts') }}
{% endblock %}

Twig: Render view with different parameters based on one Controller action

this might be a stupid question but maybe you can still help me out here.
What I want: I have a Twig template, that has tabs in it (using Bootstrap tab panels) and now I want to render the tab contents using one Action from one Controller but with different parameters.
So basically, this controller action should take some param "type" and then return different results based on this type. The goal is to not have 2 Controllers to render the twig view that do almost the same except extracting different information based on the param "type.
However, I cannot get it to work. It's either that both tabs render the same results since apparently the view is only rendered once OR that I get exceptions in the twig template at time of rendering.
What would be the right way to approach this problem?
Many thanks.
There are multiple ways to achieve this. You can use if/else statements in your Twig template, but you can also set the template in you controller. It's up to you what suits best.
Method 1: custom template
By default, Symfony uses 'foobar.html.twig' on your foobarAction() method, but you can override this in your method:
public function recentArticlesAction($max = 3)
{
// make a database call or other logic
// to get the "$max" most recent articles
$articles = ...;
return $this->render(
'article/recent_list.html.twig',
array('articles' => $articles)
);
}
(warning: example from How to Embed Controllers in a Template, but the article itself has nothing to do with your question)
You can set a variable (for example $templateName) and change it:
$templateName = 'recent_list.html.twig';
if ($admin) {
$templateName = 'another_template.html.twig';
}
//or using parameters from you Request
if ($type = $request->request->get('type')) {
$templateName = $type . '.html.twig';
}
return $this->render(
$templateName,
array('articles' => $articles)
);
Method 2: using Twig
Controller:
public function foobarAction(Request $request)
{
return [
'type' => $request->request->get('type');
];
}
You Twig template:
{% if type == 'foo' %}
<h1>Foo!</h1>
<p>Hi, welcome you the foo page!</p>
{% elseif type == 'bar' %}
<h1>Bar!</h1>
<p>Hi, you've reached the Bar page.</p>
{% else %}
<h1>Error!</h1>
<p>Type not found.</p>
{% endif %}
Try render manually each view:
if ($type) {
return $this->render('home/template.html.twig', array('data' => $data));
}
return $this->render('home/another.html.twig', array('another_data' => $another_data));
Many thanks for the answers here, but they did not really helped me. Next time I try to add more code examples ;)
My goal was to render from 1 action into 1 template but with different parameters and render those different contents as partials in the parent view.
Example: Given a template called "view.html.twig" that includes 2 times the template "myPartial.html.twig", but first time with param and second time without param. Based on this param different contents should be returned.
My question was now, why apparently only the first action is rendered in Symfony as both my partial views had the same content.
So this is what I did now:
1 Controller, 2 Actions, both call a 3rd action to fetch the data and return the values to the calling action. Then both actions call the render method with the values rendered from the 3rd action.
This is what it looks now:
<div class="tab-content clearfix">
<div class="tab-pane" id="1b">
{% render controller('MyBundle:MyController:list1') %}
</div>
<div class="tab-pane" id="2b">
{% render controller('MyBundle:MyController:list2', {'type' : 1}) %}
</div>
But what I wanted to achieve was to do something like this (which did not work because then both tabs would show the same content):
<div class="tab-content clearfix">
<div class="tab-pane" id="1b">
{% render controller('MyBundle:MyController:list') %}
</div>
<div class="tab-pane" id="2b">
{% render controller('MyBundle:MyController:list', {'type' : 1}) %}
</div>
Which I find confusing since in both times "render" is called, so I would expect that the Controller is called in both times so that also the partial view in the controller is rendered both times. But apparently this was not the case :( The Controller itself looks something like this:
public function list1Action()
{
$twigVars = //do something to build the twig vars without param;
return $this->render('#MyBundle/Test/partial_list.html.twig', array(
'vars' => $twigVars,
));
}
public function list2Action($param)
{
$twigVars = //do something to build the twig vars with param;
return $this->render('#MyBundle/Test/partial_list.html.twig', array(
'vars' => $twigVars,
));
}
While what I wanted was something like this:
public function listAction($param = '')
{
if ($param) {
$twigVars = //do something to return the twig vars with param;
} else {
$twigVars = //do something else to return twig vars without param;
}
return $this->render('#MyBundle/Test/partial_list.html.twig', array(
'vars' => $twigVars,
));
}

Render a choice field without form

I often need to render very simple imputs in some of my templates.
I'd like to take advantage of the twig macros & form blocks to render certain HTML inputs without involving the whole Symfony forms machinery.
For example from the controller:
$templateContext = array(
'my_input' = new FormField('my_input', 'choice', array(
'choices' => array('value1', 'value2', 'my_value'),
'value' => 'my_value',
));
);
In the template:
<div>{{ form_widget(my_input) }}</div>
would render:
<div>
<select name="my_input">
<option>value1</option>
<option>value2</option>
<option selected="selected">my_value</option>
</select>
</div>
Is there a simple way to do that ?
Eventually I would also like to be able to reuse these fields elsewhere (like we can reuse form types)
There are many ways to go about this. The easiest would be to write the plain HTML into your twig template.
<form method="post">
<div>
<select name="my_input">
<option>value1</option>
<option>value2</option>
<option selected="selected">my_value</option>
</select>
</div>
</form>
And then in your controller read the values back.
$request = $this->getRequest();
if($request->getMethod() == 'POST'){
$data = $request->get('my_input')
//Do whatever you want with $data
}
If you want you re-use the html, you can build it somewhere in your PHP and pass it into Twig whenever you need it; or you can place it in a separate twig template and read that with the {include ''} command in twig.
Here is what I finally ended up with:
Class MyInput {
public static values = array(
'value1',
'value2',
'my_value',
);
}
class MyInputType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'choices' => MyInput::$value,
));
}
public function getParent() { return 'choice'; }
public function getName() { return 'my_input_type'; }
}
Field type used from the controller (after having registered it as a service)
public function MyAction(Request $request)
{
$form = $this->createForm('my_input_type');
$form->handleRequest($request);
$templateContext['myForm'] = $form->createView();
// ...
}
Input rendered into the template
<div>{{ form(myForm) }}</div>
To conclude: I've not been able to render an input without the form machinery, but the actual form remains rather simple.
I found my own solution for it since i need to create subforms from existing forms.
create totally empty twig template,
add in it only {{form(form)}}
render this template render()->getContent()
do a preg replace on the result (i do it in js) formView = formView.replace(/<(.*?)form(.*?)>/, '');
Yes yes - i know that regex is not perfect so before someone says it - i say it on my own, change it for youself as it catches "form-group" classes etc as well.
This is just a showcase of my solution

collection Field Type not creating form elements

I'm trying to create a form which will add a new text box every time the 'Add new box' link got clicked.
I read through the following example.
http://symfony.com/doc/current/reference/forms/types/collection.html
Basically I was following the example from the book. But when the page is rendered and I click on the link nothing happens.
Any thoughts?
Thanks.
This is my controller.
public function createAction() {
$formBuilder = $this->createFormBuilder();
$formBuilder->add('emails', 'collection', array(
// each item in the array will be an "email" field
'type' => 'email',
'prototype' => true,
'allow_add' => true,
// these options are passed to each "email" type
'options' => array(
'required' => false,
'attr' => array('class' => 'email-box')
),
));
$form = $formBuilder->getForm();
return $this->render('AcmeRecordBundle:Form:create.html.twig', array(
'form' => $form->createView(),
));
}
This is the view.
<form action="..." method="POST" {{ form_enctype(form) }}>
{# store the prototype on the data-prototype attribute #}
<ul id="email-fields-list" data-prototype="{{ form_widget(form.emails.get('prototype')) | e }}">
{% for emailField in form.emails %}
<li>
{{ form_errors(emailField) }}
{{ form_widget(emailField) }}
</li>
{% endfor %}
</ul>
Add another email
</form>
<script type="text/javascript">
// keep track of how many email fields have been rendered
var emailCount = '{{ form.emails | length }}';
jQuery(document).ready(function() {
jQuery('#add-another-email').click(function() {
var emailList = jQuery('#email-fields-list');
// grab the prototype template
var newWidget = emailList.attr('data-prototype');
// replace the "$$name$$" used in the id and name of the prototype
// with a number that's unique to our emails
// end name attribute looks like name="contact[emails][2]"
newWidget = newWidget.replace(/\$\$name\$\$/g, emailCount);
emailCount++;
// create a new list element and add it to our list
var newLi = jQuery('<li></li>').html(newWidget);
newLi.appendTo(jQuery('#email-fields-list'));
return false;
});
})
</script>
This problem can be solved by referring to the following link.
https://github.com/beberlei/AcmePizzaBundle
Here you will find the same functionality being implemented.
I've been through this too.
Answer and examples given to this question and the other question I found did not answer my problem either.
Here is how I did it, in some generic manner.
In generic, I mean, Any collection that I add to the form just need to follow the Form template loop (in a macro, for example) and that's all!
Using which convention
HTML is from Twitter Bootstrap 2.0.x
Javascript code is already in a $(document).ready();
Following Symfony 2.0.x tutorial
Using MopaBootstrapBundle
Form Type class
class OrderForm extends AbstractType
{
// ...
public function buildForm(FormBuilder $builder, array $options)
{
// ...
$builder
->add('sharingusers', 'collection', array(
'type' => new UserForm(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'required'=> false
));
// ...
}
}
JavaScript
/* In the functions section out of document ready */
/**
* Add a new row in a form Collection
*
* Difference from source is that I use Bootstrap convention
* to get the part we are interrested in, the input tag itself and not
* create a new .collection-field block inside the original.
*
* Source: http://symfony.com/doc/current/cookbook/form/form_collections.html
*/
function addTagForm(collectionHolder, newBtn) {
var prototype = collectionHolder.attr('data-prototype');
var p = prototype.replace(/\$\$name\$\$/g, collectionHolder.children().length);
var newFormFromPrototype = $(p);
var buildup = newFormFromPrototype.find(".controls input");
var collectionField = $('<div class="collection-field"></div>').append(buildup);
newBtn.before(collectionField);
}
/* ********** */
$(document).ready(function(){
/* other initializations */
/**
* Form collection behavior
*
* Inspired, but refactored to be re-usable from Source defined below
*
* Source: http://symfony.com/doc/current/cookbook/form/form_collections.html
*/
var formCollectionObj = $('form .behavior-collection');
if(formCollectionObj.length >= 1){
console.log('run.js: document ready "form .behavior-collection" applied on '+formCollectionObj.length+' elements');
var addTagLink = $('<i class="icon-plus-sign"></i> Add');
var newBtn = $('<div class="collection-add"></div>').append(addTagLink);
formCollectionObj.append(newBtn);
addTagLink.on('click', function(e) {
e.preventDefault();
addTagForm(formCollectionObj, newBtn);
});
}
/* other initializations */
});
The form template
Trick here is that I would have had used the original {{ form_widget(form }} but I needed to add some specific to the view form and I could not make it shorter.
And I tried to edit only the targeted field and found out it was a bit complex
Here is how I did it:
{# All form elements prior to the targeted field #}
<div class="control-collection control-group">
<label class="control-label">{{ form_label(form.sharingusers) }}</label>
<div class="controls behavior-collection" data-prototype="{{ form_widget(form.sharingusers.get('prototype'))|escape }}">
{% for user in form.sharingusers %}
{{ form_row(user) }}
{% endfor %}
</div>
</div>
{{ form_rest(form) }}

How to load a controller function and render it in a twig tag using Symfony2?

I am using Symfony2 and Twig. I have a function (below) in my controller that returns a specific text. Is it possible to call that function directly from my template and change the {{text}} in my template to whatever the function returns, possibly via Ajax?
Here's my function:
public function generateCode($url) {
$url = $_SERVER['SERVER_NAME'] . '/embed/' . $url;
$return = '<iframe>'.$url.'</iframe>';
return $return;
}
Another controller function calls the function above and renders my template:
public function getCodeAction($url) {
$text = $this->generateCode($url);
return $this->render('MyMyBundle:User:code.html.twig', array('text' => $text));
}
In my template I am using:
{{ text }}
to display the value.
In Symfony 2.2, this was changed.
The render tag signature and arguments changed.
Before:
{% render 'BlogBundle:Post:list' with { 'limit': 2 }, { 'alt': BlogBundle:Post:error' } %}
After:
{% render controller('BlogBundle:Post:list', { 'limit': 2 }), { 'alt': 'BlogBundle:Post:error' } %}
or
{{ render(controller('BlogBundle:Post:list', { 'limit': 2 }), { 'alt': 'BlogBundle:Post:error'}) }}
Note: The function is the preferred way.
See https://github.com/symfony/symfony/blob/2.2/UPGRADE-2.2.md
You can use ajax if you have dynamic data, but as far as I can see from your brief info, you can always execute that controller function directly from your view:
{% render "MyMyBundle:User:generateCode" with { 'url': 'your url here' } %}
More Information on this available at:
http://symfony.com/doc/2.0/quick_tour/the_view.html, under Embedding other Controllers
For the record, in new versions you need to use the absolute URL:
{{ render url('my_route_id', {'param': value}) }}
{{ render(controller("AcmeDemoBundle:Demo:topArticles", {'num': 10})) }}
In Silex I solved it like this:
{{ render(url('route_name', {'param': value})) }}
If you do not have the route name, URL can be used:
{{ render(app.request.baseUrl ~ '/some-path/' ~ value) }}
If using URL we should always concat the baseUrl.
Symfony 2.6+
in twig:
{{ render(controller('AppBundle:PropertySearch:featuredProperties', {'limit': 15})) }}
controller:
/**
* featuredPropertiesAction
*
* #param Request $request
* #param int $limit
*
* #return Response
*/
public function featuredPropertiesAction(Request $request, $limit)
{
$search = $this->resultsHelper->featuredSearch($limit);
return $this->render('HASearchBundle::featured_properties.html.twig', [
'search' => $search,
]);
}

Resources