Symfony/Twig: Extending stylesheet block from an extended bundle - symfony

I'd like to extend the stylesheets blocks from inside a bundle I've created to extend FOSUserBundle.
This is how to extend from inside the same bundle, but I want to extend from another bundle that extends a third-party bundle.
This is the code:
{# src/MyNamespace/UserBundle/views/layout.html.twig #}
{% extends '::base.html.twig' %}
{% block stylesheets %}
{{ parent() }}
{% stylesheets filter='scssphp,cssrewrite' output='css/app.css'
'bundles/mynamespace/scss/Profile.scss'
%}
{% endstylesheets %}
{% endblock %}
{% block title %}My title{% endblock %}
{% block body %}
{% block fos_user_content %}{% endblock %}
{% endblock %}
So, basically, I've extended the FOSUserBundle creating another bundle in my src directory as explained here.
The code of my `base.html.twig template is this:
{# app/Resources/views/base.html.twig #}
...
<head>
...
<title>{% block title %}{% endblock %}</title>
<meta name="description" content="{% block metaDescription %}{% endblock %}" />
{% include('::_common/Stylesheets.html.twig') %}
...
And this is the included Stylesheets.html.twig:
{% block stylesheets %}
{% stylesheets filter='scssphp,cssrewrite' output='css/app.css'
'bundles/app/css/bootstrap.css'
...
%}
<link rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}
{% endblock %}
The problem is that the CSS from src/MyNamespace/UserBundle/views/layout.html.twig is never included in the page, also if it is installed with app/console asstes:install and then dumped with app/console assetic:dump.
The CSS is generated and put in web/css/app_Profile_1.css but is never included in the pages (not in home page or other AppBundle pages, not in MyNamespaceUserBundle pages.
Also adding the line <link rel="stylesheet" href="{{ asset_url }}" /> to the src/MyNamespace/UserBundle/views/layout.html.twig block, the CSS isn't included.
Obviously, if I include the stylesheet directly from _common/Stylesheets.html.twig the file is correctly included.
So, it seems there is something break.
Anyone can help me?

Without the <link rel="stylesheet"> in the {% block stylesheets %} in layout.html.twig, it's not going to output the link for Profile.scss.
More importantly, though, is that {% include %} and {% block %} do not work quite the way you think they do in the presence of {% extends %}. The combination is a little weird, so some background first.
A {% block %} is simply a named wrapper around some content. In the absence of {% extends %}, a block is immediately output where it is defined. (Think of it as if you defined a function, and then immediately called it.)
When you extend another template, however, block definitions in the "global scope" of the extending template replace the definitions of the block in the extended template. This is a re-definition only; the block is still being called at the same place it was in the parent template.
{% include %} renders another template and inserts the results of the evaluation in-line. While it has access to the current context's variables, it is otherwise it's own separate renderer. Importantly, it does not substitute itself with the template code as you are expecting.
So what is happening is this:
layout.html.twig extends base.html.twig. layout.html.twig defines a stylesheets block (but does not emit it). base.html.twig includes Stylesheets.html.twig, so base.html.twig gains the result of evaluating Stylesheets.html.twig (so you get the contents of the {% stylesheets %} tag, but importantly, not the fact that there was even a block there at all.
Then, because there is no place in base.html.twig where there actually is a {% block stylesheets %}, the block defined in layout.html.twig never gets called.
The solution is to move the {% block stylesheets %} into base.html.twig. If you want to keep the actual stylesheet definitions in the Stylesheets.html.twig, then you can do it like this:
{% block stylesheets %}
{% include('::_common/Stylesheets.html.twig') %}
{% endblock %}
And remove the block tag (but not the stylesheet content itself) from Stylesheets.html.twig. That should give you the desired result.

Related

Assetics ou Gassetics?

I just run on Gassetics, seems to be the next gen of SF assets management. So far I used Assetics.
One question though : with Assetics I used to split files in order to load only needed one, using Twig parent() method :
{% block scripts %}
{{ parent() }}
{% javascripts
'#LCHAdminBundle/Resources/public/js/jquery.specific.addition.js'
%}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
{% endblock scripts %}
SO I could add on a specific page, only script needed and so was "forced" (in the noble way) to think and split my twig files accordingly.
Is it possible to do so with Gassetics? I jsut saw that you can specify back-end and front-end files, nothing more.
If I nest Twig files with Gassetic tag, will it do the trick?
EDIT : thanks to Wouter J. comment, I adjust my question :
In Assetics you explicitely specify files you want to include, giving the nesting ability. As in Gassetics you just add the tag which will be replaced during CSS/JS file generation, how do you achieve the same?
You can do all of this in the gassetic config (see section yaml example with gassetic.yml in https://github.com/romanschejbal/gassetic).
Example gassetic.yml:
js:
files:
common.js:
- assets/vendor/jquery/jquery.js
- assets/vendor/angular/angular.js
page1.js:
- assets/vendor/lchadminbundle/jquery.specific.addition.js
common.html.twig:
{% block scripts %}
<!-- prod:common.js --><!-- endbuild -->
{% endblock scripts %}
page1.html.twig
{% extends 'common.html.twig' %}
{% block scripts %}
{{ parent() }}
<!-- prod:page1.js --><!-- endbuild -->
{% endblock scripts %}
This approach is better than nesting scripts in children templates, since the commonly needed files can be cached between page requests.

Custom data collector for profile is visualy broken

I have created the custom data collector for profiler as described here http://symfony.com/doc/current/cookbook/profiler/data_collector.html .
Now everything is working correctly except of {% block panel %} in template which defines the content area for my information. I have everything in place but on the screen tha page is almost without styles so its visualy broken.
When I compare my panel page and some other from profiler the mine lacks of cca. 200 lines of styles that Symfony is adding to the page header to style the page. I miss style for #content as an example. I tried to clear cache and refreshed everything but still now way. Any idea what is going to be wrong here?
This is waht I see in the browser ...
EDITED: Template
{% extends 'WebProfilerBundle:Profiler:layout.html.twig' %}
{% block toolbar %}
{% endblock %}
{% block head %}
{% endblock %}
{% block menu %}
<span class="label">
<strong>NET.Notes</strong>
</span>
{% endblock %}
{% block panel %}
panel david
{% endblock %}
It is due to empty block of "head". Rust remove it or include parent block by {{parent()}}.
{% block head %}
{#if the web profiler panel needs some specific JS or CSS files #}
{% endblock %}

Twig the same block in different included files

I have problem with included files. In my layout.html.twig I have scripts.js at the end body section before {% block script %}{% endblock %}. When file extending layout and use script block, its ok, but when use this block and include file whose use the same block, then is problem. Rendered page has all javascripts, but in different places.
For example:
page.html.twig
...
There is page.html.twig
{% include 'file.html.twig' with {'something': 'something'} %}
After include file.html.twig
..
{% block script %}
<script src="file1.js"></script>
{% endblock %}
file.html.twig
There is file.html.twig
{% block script %}
<script src="file2.js"></script>
{% endblock %}
Then rendered page look like this:
There is page.html.twig
There is file.html.twig
<script src="file2.js"></script>
After include file.html.twig
<script src="file1.js"></script>
I want have all javascript files in one place, one by one.
I think you should try to 'embed' instead of 'include'.
http://twig.sensiolabs.org/doc/tags/embed.html
With embedded, you can choose blocks to include.
Ps : If you override a block, you can get the parent block content in using parent() :
{% block script %}
{{ parent() }}
{% endblock script %}
The {% block %} tag and the inheritance system work only with layouts and {% extends %}. It is not meant to be used with includes, so the script block in your included file.html.twig does not merge with page.html.twig.
One solution would be to set a an argument to you included file.
file.html.twig
{% if get == 'content' %}
There is file.html.twig
{% endif %}
{% if get == 'script' %}
<script src="file2.js"></script>
{% endif %}
page.html.twig
...
There is page.html.twig
{% include 'file.html.twig' with {'something': 'something', 'get': 'content'} %}
After include file.html.twig
..
{% block script %}
<script src="file1.js"></script>
{% include 'file.html.twig' with {'something': 'something', 'get': 'script'} %}
{% endblock %}
You will need to conform to a standard practice when dealing with included templates and inheritance (you can invent your own standard).
Try to compartmentalize your includes, I usually have a directory called 'partials' for includes, and 'fragments' for renders. Each one belonging to a single collection of controller views.
One way I dealt with a similar problem to what you are having was to use a base template which covered the requirements of a specific set of views, each view template would extend it. It may be somewhat wasteful to include the javascripts and stylesheets for the entire collection of views for a specific controller, but it is a) more efficient that including all assets everywhere and, b) I manage the view specific assets under a single base template.
So long as the views have a dependency on that base template the structure wouldn't break.
Think of Twig templates as PHP classes (they compile to classes anyway). A class can inherit from one chain of parents. What you are trying to do is treat two sub-classes as a single child of a super class, overriding the same method at the same time. Simply can't be done. An include is closer to a child property, with is own rules and properties. The included template is less dependent on the includer than vice-versa, so it is impossible for it to inherit from it conventionally.

Embedding contents of css and javascript assets into the view in Symfony

I have a one-page webapp written with Symfony 2 where all css and javascript assets sit in web/static/all.js and web/static/all.js. These a linked from the intex.html.twig template in a standard way:
{% javascripts output='static/all.js' filter="?closure"
'#MyBundle/Resources/assets/vendor/jquery/jquery.js'
'#MyBundle/Resources/assets/...'
'#MyBundle/Resources/assets/...'
%}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}
{% stylesheets output='staic/all.css' filter="?yui_css" combine=true
'#MyBundle/Resources/assets/css/reset.css'
'#MyBundle/Resources/assets/css/...'
'#MyBundle/Resources/assets/css/...'
%}
<link rel="stylesheet" href="{{ asset_url }}" type="text/css" media="all" />
{% endstylesheets %}
This is pretty cool already, because the browser only has to do 3 requests during the first launch and 1 or 0 requests later (all.css and all.js are cached using .htaccess).
But I want to go further and just dump all css and js into index.html similar to what google does on their homepage. This will result only one http request during the first run, which will speed up the app a little. It would be ideal to have a flag that I pass to the template or keep in twig globals that will enable switching between two modes on demand.
{%if dumpAssetsIntoTemplate %}
{# all js and css here #}
{% else %}
{# asset urls #}
{% endif %}
How could this be done with minimum pain?
If you look at Twig reference about Twig extensions you can see that the render function takes a path or a URL.
{{ render(path('route', {params})) }}
{{ render(url('route', {params})) }}
Quoting the usage from the doc "This will render the fragment for the given controller or URL".
So I would try that:
<style type="text/css">
{% stylesheets output='static/all.css' filter="?yui_css" combine=true
'#MyBundle/Resources/assets/css/reset.css'
'#MyBundle/Resources/assets/css/...'
'#MyBundle/Resources/assets/css/...'
%}
{{ render(asset_url) }}"
{% endstylesheets %}
</style>
Same approach with your Javascript files.
If it doesn't work, you'll probably want to tweak the settings of the Assetic bundle in particular the setting named use_controller.

Twig Template Asset Resolution

I'm having problems with my child twig templates properly resolving their assets.
My assets lie in web/bundles/mlbp/images|js|css
In my parent twig template that all my other templates inherit from I have something like:
{% block javascripts %}
<script src="{{ asset('bundles/mlbp/js/jQuery.js') }}" type="text/javascript"></script>
{% endblock %}
When looking at source this resolves properly to /bundles/mlbp/js/jQuery.js
But in one of my child templates I'm doing something like this:
{% block javascripts %}
{{parent()}}
<script src="{{ asset('bundles/mlbp/js/tableSortInit.js') }}" type="text/javascript"></script>
{% endblock %}
This for some reason resolves to /js/tableSortInit.js which does not exist. I don't see why it would work in one but not the other so any help will be very appreciated
You should try checking any other templates that relate to that one, it may be coming from those especially if you do a lot of in template rendering etc.

Resources