Symfony3 Highcharts pie chart blank - symfony

I started with Highcharts today in a symfony3 project and have the line charts working great. I'm having trouble rendering a pie chart and I'm beating my head on the wall trying to figure out why it's not working.
I can reproduce the basic pie chart (like this one) if I render directly in javascript in my twig template, and it looks as expected. BUT if I try to render from my controller I get a blank image where the pie chart should be. My chart title is there so I know I'm rendering to the correct div. But the chart is blank. I've even tried very simple data structures (like an array of numbers as suggested by the docs) with no luck. This must be something simple. Help!
My controller:
public function piechartAction()
{
$data = [["name"=> 'Microsoft Internet Explorer', "y"=> 56.33],
["name"=> 'Chrome', "data"=> 24.03],
["name"=> 'Firefox', "y"=> 10.38],
["name"=> 'Safari', "y"=> 4.77],
["name"=> 'Opera', "y"=> 0.91],
["name"=> 'Proprietary or Undetectable', "y"=> 0.2]];
$ob = new Highchart();
$ob->chart->renderTo('container');
$ob->chart->type('pie');
$ob->title->text('My Pie Chart');
$ob->series(array("data"=>$data));
return $this->render('dashboard/test.html.twig', [
'mypiechart' => $ob
]);
}
And my twig template:
{% extends 'base.html.twig' %}
{% block javascripts %}
{{ parent() }}
<script src="//code.highcharts.com/highcharts.js"></script>
<script src="//code.highcharts.com/modules/exporting.js"></script>
<script>
jQuery(document).ready(function() {
{{ chart(mypiechart) }}
});
</script>
{% endblock %}
{% block body %}
<div id="container" style="min-width: 310px; height: 400px; max-width: 600px; margin: 0 auto"></div>
{% endblock %}

I solved this. For anyone looking in the future, here's what I had wrong:
My setting of series data was not the correct format.
Here's my new (working) controller:
public function piechartAction()
{
$data = [
['Microsoft Internet Explorer', 56.33],
['Chrome', 24.03],
['Firefox', 10.38],
['Safari', 4.77],
['Opera', 0.91],
['Proprietary or Undetectable', 0.2]
];
$ob = new Highchart();
$ob->chart->renderTo('container');
$ob->chart->type('pie');
$ob->title->text('My Pie Chart');
$ob->series(array(array("data"=>$data)));
return $this->render('dashboard/test.html.twig', [
'mypiechart' => $ob
]);
}
I found this by studying the "cookbook" associated with this bundle (here); there is a listing for a drill-down pie chart.

Related

reusable dynamic sidebar in Symfony 4 (Twig)?

I recently started using Symfony 4 and I am creating my first website with this wonderful framework right now.
I have a sidebar that should be displayed in about half of my routes and the content of the sidebar should be filled with some data from a database.
Currently I use DI in all this routes and pass the result of the injected repository to the template (which includes my sidebar.html.twig) for the route.
public function chalupaBatman(FancyRepository $repository)
{
$sidebarObjects = $repository->getSidebarObjects();
$this->render('controllername/chalupabatman.html.twig', [
'sidebarObjects' => $sidebarObjects,
]);
}
I am wondering if there is a way to avoid this for every route I define in my controllers.
So far I found this topic on stackoverflow.
The User Mvin described my problem in a perfect way and also provided some solutions.
However there is still no answer to "what is the best practice" part also the topic is from 2017; therefor, the way to solve this may have changed in Symfony 4.
I ended up with a TwigExtension solution. I'll describe how to achieve it and it would be great if you guys could provide some feedback.
Let me know if I produce massive overhead or miss something essential ;-)
Alright, first of all I created a TwigExtension via command-line
php bin/console make:twig-extension AppExtension
And then I modified the class to look like this:
<?php
namespace App\Twig;
use App\Repository\ArticleRepository;
use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class AppExtension extends AbstractExtension implements ServiceSubscriberInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getFunctions(): array
{
return [
new TwigFunction('article_sidebar', [$this, 'getArticleSidebar'], ['needs_environment' => true, 'is_safe' => ['html']]),
];
}
public function getArticleSidebar(\Twig_Environment $twig)
{
$articleRepository = $this->container->get(ArticleRepository::class);
$archive = $articleRepository->myAwesomeLogic('omegalul');
return $twig->render('/article/sidebar.html.twig', [
'archive' => $archive,
]);
}
public static function getSubscribedServices()
{
return [
ArticleRepository::class,
];
}
}
In order to activate Lazy Performance so our Repository and the additional Twig_Environment doesn't get instantiated everytime when we use Twig
we implement the ServiceSubscriberInterface and add the getSubscribedServices-method.
Therefor, our Repo and Twig_Environment only gets instantiated when we actually call {{ article_sidebar() }} inside a template.
{# example-template article_base.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<div class="row">
<div class="col-10">
{% block article_body %}{% endblock %}
</div>
<div class="col-2">
{{ article_sidebar() }}
</div>
</div>
{% endblock %}
Now I am able to define my templates for the article-routes like this:
{# example-template /article/show.html.twig #}
{% extends 'article_base.html.twig' %}
{% block article_body %}
{# display the article here #}
{% endblock %}

How do I pass variables to theme template via custom controller drupal 8

I currently have a theme I purchased installed on my Drupal website.
Inside the theme folder there is a templates folder that contains:
page.html.twig
page--front.html.twig
a snippet from page.html.twig looks as follows:
<footer class="site-footer">
<div class="layout-container">
{% if page.footer_first or page.footer_second or page.footer_third %}
<div class="footer-top">
<div class="footer-top-inner">
{{ page.footer_first }}
{{ page.footer_second }}
{{ page.footer_third }}
</div>
</div>
{% endif %}
{% if page.footer_left or page.footer_right %}
<div class="footer-bottom">
<div class="footer-bottom-inner">
{{ page.footer_left }}
{{ page.footer_right }}
</div><!-- /.footer-bottom-inner -->
</div>
{% endif %}
</div>
</footer>
I have a custom module I created (business_listing) that loads different .html.twig templates I add these templates in business_listing.module:
function business_listing_theme($existing, $type, $theme, $path) {
return [
// business-listing-detail.html.twig
'business_listing_detail' => [
'variables' => ['data' => [], 'slides' => [], 'paths' => [], 'page' => []],
],
]
}
Basically I would like to know how I can add the markup for the footer dynamically from my custom modules controller.
I have a page called business-listing-detail.html.twig inside my custom modules templates folder. From what I understand business-listing-detail.html.twig uses/somehow extends page.html.twig in my theme/templates. What I would now like to know is how I can add the sections like {{page.footer_left}} to my business-listing-detail.html.twig or to page.html.twig using my controller? I have tried adding the footer using the following: `
$page['footer_left'] = "This should appear in the .footer-bottom div?";
$build = [];
$build['business_listing_detail'] = [
'#theme' => 'business_listing_detail',
'#data' => $record,
'#slides' => $slides,
'#paths' => $this->paths,
'#page' => $page
];`
in the controller function associated to my business-listing-detail page the hopes that page.html.twig will detect this page.footer_left and render display the footer, however this did not work. The variable exists in business-listing-detail.html.twig but the {% if page.footer_left %} in my themes page.html.twig is not fired. Please please please, any help or advice would be greatly appreciated <3
Basically all I am trying to do, is dynamically render a template for a specific page in my custom module, that allows me to implement/send markup to the sections/regions defined in my theme eg. featured_top, content_top & page.content
Kind regards,
Matt
It seems like you might be going down the wrong path... I'd highly suggest you read up about the Drupal 8 Block system, and then investigate custom blocks.

Twig: Can a child template override a block from a file included in the parent template?

I have a largish base.twig file, and I want to break it up into three includes: header.twig, content.twig, and footer.twig. I'm having trouble getting the block from my child template to override the block included into my parent template and would like to know if it's even possible, and if not, what a Twig-ish solution might look like.
I've setup a simple example to illustrate the question. I'm retrieving a Wordpress page and using Timber to process the Twig templates. The PHP template that gets invoked is page-test.php:
<?
$context = Timber::get_context();
Timber::render('test_child.twig', $context);
?>
The Twig template that gets rendered is test_child.twig:
{% extends 'test_base.twig' %}
{% block content_main %}
<h1>Random HTML</h1>
{% endblock content_main %}
The parent template, test_base.twig is:
<!DOCTYPE html>
<head>
<title>Twig test</title>
</head>
<body>
{% include 'test_content.twig' %}
</body>
</html>
And finally, the included template, test_content.twig, is like this:
<div class="main">
{% block content_main %}
{% endblock content_main %}
</div>
The resulting output looks like this:
<!DOCTYPE html>
<head>
<title>Twig test</title>
</head>
<body>
<div class="main">
</div>
</body>
</html>
As you can see, the <div> has no content. What I expected was for it to contain the <h1>Random HTML</h1> fragment from test_child.twig.
Why isn't the block from test_child.twig overriding the same-named block included from test_content.twig into test_base.twig? And if the approach simply won't work, what's the best Twig-ish way of accomplishing something close?
This is indeed not possible with twig, due to the fact included files have no affinity with the templates who called them. To explain myself have a look at this snippet
{{ include('foo.twig') }}
This snippet will be parsed into PHP by the twig compiler and the code it compiles into is this
$this->loadTemplate("foo.twig", "main.twig", 6)->display($context);
Now we can investigate this further with looking at the source of Twig_Template::loadTemplate. If you have a look at that particular function we will see, that because u are passing a string to the function, the function loadTemplate will be called in the class Twig_Environment
In this last function we can cleary see that the Twig_Environment::loadTemplate function is not passing any information nor instance of the template you rendered towards the template you are including. The only thing that gets passed (by value) is the variable $context, which hold all variables you've sent from your controllller to the template you are rendering.
I'm guessing one of the main reasons this is coded as such is because included files should be reusable in any situation and should not have dependencies like a (non-existant) block to make them being rendered
TwigTemplate.php
protected function loadTemplate($template, $templateName = null, $line = null, $index = null) {
try {
if (is_array($template)) return $this->env->resolveTemplate($template);
if ($template instanceof self) return $template;
if ($template instanceof Twig_TemplateWrapper) return $template;
return $this->env->loadTemplate($template, $index);
} catch (Twig_Error $e) {
if (!$e->getSourceContext()) $e->setSourceContext($templateName ? new Twig_Source('', $templateName) : $this->getSourceContext());
if ($e->getTemplateLine()) throw $e;
if (!$line) {
$e->guess();
} else {
$e->setTemplateLine($line);
}
throw $e;
}
}
Environment.php
public function loadTemplate($name, $index = null) {
$cls = $mainCls = $this->getTemplateClass($name);
if (null !== $index) {
$cls .= '_'.$index;
}
if (isset($this->loadedTemplates[$cls])) {
return $this->loadedTemplates[$cls];
}
if (!class_exists($cls, false)) {
$key = $this->cache->generateKey($name, $mainCls);
if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) {
$this->cache->load($key);
}
if (!class_exists($cls, false)) {
$source = $this->getLoader()->getSourceContext($name);
$content = $this->compileSource($source);
$this->cache->write($key, $content);
$this->cache->load($key);
if (!class_exists($mainCls, false)) {
/* Last line of defense if either $this->bcWriteCacheFile was used,
* $this->cache is implemented as a no-op or we have a race condition
* where the cache was cleared between the above calls to write to and load from
* the cache.
*/
eval('?>'.$content);
}
if (!class_exists($cls, false)) {
throw new Twig_Error_Runtime(sprintf('Failed to load Twig template "%s", index "%s": cache is corrupted.', $name, $index), -1, $source);
}
}
}
As I'm not sure why you have this set-up,this would be a more twig-style setup. Please note you have to define the blocks in your base class first, because "defining" block in an extended class, tries to display the blocks of your parent and does not create a new one.
test_main.twig
<!DOCTYPE html>
<head>
<title>Twig test</title>
</head>
<body>
{% block content_main %}
{% include 'block.twig' %}
{% endblock %}
</body>
</html>
test_child.twig
{% extends "test_main.twig" %}
{% block content_main %}
{% include "test_content.twig" %}
{% endblock %}
test_content.twig
<div class="main">
Lorem Lipsum
</div>
Unfortunately this does not work with include.
I had this issue as well when I was trying to pass some SEO values from my product controller to the base template that included the meta tags.
You have to use "extends" for the inner template as well, and point your controller to use the inner template instead of the middle/layout one.
You can then define a separate block on your inner template, which can directly override the base template's block.
You can see a working example in this Fiddle (Note that the inner template is the main one)
https://twigfiddle.com/1ve5kt

Save charts on server

I have a symfony3 project with working Highcharts functions implemented through the ob/highcharts-bundle and I really like the way the generated charts look with a custom theme.
I would like to be able to run a script on the server that generates an email and along the way builds the charts that I need (complete with my custom theme) and saves to the server so I can link to them from the email.
I've previously done this in pChart, and saving an image on the server from a script is as easy as $myPicture->autoOutput('myimage.png'). While that's easy, I prefer the look of charts from Highcharts.
Is there a similar simple way to do this something like this using Highcharts?
My scripts look like this:
// Controller
$xdata = array(1, 2, 3, 4);
$data_series = array(
0 => array(
"name" => "Series 1",
"data" => array(
0 => 2.0, 1 => 0.0, 2 => 5.0, 3 => 2.3, 4 => 0.45, 5 => 0.4
)
)
);
$ob = new Highchart();
$ob->chart->renderTo('trendchart'); // id of the div where the chart gets rendered
$ob->title->text('Chart title');
$ob->xAxis->categories($xdata);
$ob->series($data_series);
return $this->render('dashboard/main.html.twig', [
'trendChart' => $ob,
]);
and I render this in a twig template:
// twig template
{% extends 'base.html.twig' %}
{% block javascripts %}
{{ parent() }}
<script src="//code.highcharts.com/highcharts.js"></script>
<script src="//code.highcharts.com/modules/exporting.js"></script>
<script src="{{ asset('js/highchartThemes/mytheme.js') }}"></script>
<script>
{{ chart(trendChart) }}
</script>
{% endblock %}
{% block body %}
<div id="trendchart"></div>
{% endblock %}
What I'm hoping for is to make a call from my controller like:
$ob->save('myimage.png');
Does anyone know of a simple/clean way to do something like this?

How can I print Google Books api description?

Hie I am trying to get the synopsis and other items like author and published date printed. But I am Able to achieve this only with certain search terms, an error occurs with other words or terms
Key "description" for array with keys "title, subtitle, authors, publishedDate, industryIdentifiers, readingModes, pageCount, printType, categories, maturityRating, allowAnonLogging, contentVersion, imageLinks, language, previewLink, infoLink, canonicalVolumeLink" does not exist.
I am using symfony and twig. this is what the twig file looks like :
{% for item in items %}
<article>
<img src="{{ item.volumeInfo.imageLinks.thumbnail}}"/>
<h4>{{ item.volumeInfo.title}}</h4>
{{ item.volumeInfo.description }}
<strong> {{ item.volumeInfo.publishedDate }}</strong><br/>
<b>{{ item.volumeInfo.authors | join }}</b>
</article>
What am I doing wrong? why does this work only sometimes ? how can I make it work correctly all the time?
class GoogleBooksController extends Controller
{
public function getVolumeAction($title)
{
$client =new client();
$response = $client- >get("https://www.googleapis.com/books/v1/volumes?q=$title");
$data=$response->json();
$items=$data['items'];
return $this->render('BookReviewBundle:GoogleBooks:volume.html.twig', array('items'=>$items
// ...
)); }
Thanks
I belive the description field is not mandatory, so you can do follow
{% if item.volumeInfo.description is defined %}
{{ item.volumeInfo.description }}
{% endif %}

Resources