Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony 2 custom form field type: how to add javascript and css only once?

I want to use javascript in custom Symfony 2 form field type extension. So, I have Twig extension template like this:

{% block some_widget %}
    <input ... />

    <script src="some.js"></script>
    <link href="some.css" />
{% endblock %}

But I want to have these script and link tags only once in my HTML, ideally in head tag, without modifing base template. I tried to extend Twig blocks, but I have no access to action template blocks inside form template. Or maybe something like this:

{# widget tempate #}
{% block some_widget %}
    <input ... />

    {{ use_javascript('some.js') }}
    {{ use_css('some.css') }}
{% endblock %}

{# main action template #}
...
<head>
{{ dump_javascripts() }}
{{ dump_css() }}
</head>
...

How to do this with Symfony 2 Forms + Twig?

P.S. Sorry for my bad english.

like image 986
Ilya Pleshakov Avatar asked Apr 04 '12 10:04

Ilya Pleshakov


1 Answers

I had to write a self contained form widget that requires javascript, I was able to achieve what you are trying to do through the event_dispatcher listening on the kernel.response to append the javascript at the end of the Symfony\Component\HttpFoundation\Response. Here's a snippet of my form type :

<?php

namespace AcmeBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;

class AcmeFileType extends AbstractType{

  private $twig;
  private $dispatcher;

  public function __construct(\Twig_Environment $twig, EventDispatcherInterface $dispatcher){
    $this->twig = $twig;
    $this->dispatcher = $dispatcher;
  }

  public function buildView(FormView $view, FormInterface $form, array $options){

    $javascriptContent = $this->twig->render('AcmeBundle:Form:AcmeFileType.js.twig', array());

    $this->dispatcher->addListener('kernel.response', function($event) use ($javascriptContent) {

      $response = $event->getResponse();
      $content = $response->getContent();
      // finding position of </body> tag to add content before the end of the tag
      $pos = strripos($content, '</body>');
      $content = substr($content, 0, $pos).$javascriptContent.substr($content, $pos);

      $response->setContent($content);
      $event->setResponse($response);
    });

  }

  ... 

When you define your form type in your services.yml it looks like this :

acme.form.acme_file_type:
    class: AcmeBundle\Form\AcmeFileType
    arguments:
        - @twig
        - @event_dispatcher
    tags:
        - { name: form.type, alias: acmefile }

So now, everytime you build a form with acmefile the javascript will be appended to the <body>. This solution does not prevent the javascript from being present multiple time though, but you should easily be able to improve this to suit your needs.

You can also play around with the $response object to modify the headers instead if you wish.

like image 176
Philippe Gagnon Avatar answered Sep 21 '22 13:09

Philippe Gagnon