Viewed   160 times

Is it possible (and how) to

  • determine if a user is using a mobile device
  • force symfony 2 to load different template in that case
  • (and fall back the default html template)

What id like to do is, to load different templates without modifying any controller.

UPDATE

It wasn't the detection part the real issue here, it's really nothing to do with symfony. It can be done (load different template) on a controller level:

public function indexAction()
{
    $format = $this->isMobile() ? 'mob' : 'html';
    return $this->render('AcmeBlogBundle:Blog:index.'.$format.'.twig');
}

But can it be done globally? Like a service, or something that execute before every request, and make changes in the templating rules.

 Answers

2

Ok, so I don't have a full solution but a little more than where to look for one :)

You can specify loaders (services) for templating item in app/config/config.yml

framework:
    esi:             { enabled: true }
    #translator:     { fallback: %locale% }
    secret:          %secret%
    router:
        resource: "%kernel.root_dir%/config/routing.yml"
        strict_requirements: %kernel.debug%
    form:            true
    csrf_protection: true
    validation:      { enable_annotations: true }
    templating:       
        engines: 
           - twig 
        loaders:  [moby.loader]
    default_locale:  %locale%
    trust_proxy_headers: false
    session:         ~

Then define the mentioned loader service:

services:
    moby.loader:
        class: AcmeAppBundleTwigLoaderMobyFilesystemLoader
        arguments:    ["@templating.locator", "@service_container"]

After that define your loader service class:

namespace AcmeAppBundleTwigLoader;

use SymfonyBundleFrameworkBundleTemplatingLoaderFilesystemLoader;
use SymfonyComponentTemplatingStorageFileStorage;


class MobyFilesystemLoader extends FilesystemLoader
{
     protected $container;

     public function __construct($templatePathPatterns, $container) 
     {
         parent::__construct($templatePathPatterns);
         $this->container = $container;
     }

     public function load(SymfonyComponentTemplatingTemplateReferenceInterface $template)
     {
         // Here you can filter what you actually want to change from html
         // to mob format
         // ->get('controller') returns the name of a controller
         // ->get('name')  returns the name of the template
         if($template->get('bundle') == 'AcmeAppBundle') 
         {
            $request = $this->container->get('request');
            $format = $this->isMobile($request) ? 'mob' : 'html';

            $template->set('format', $format);
         }

         try {
            $file = $this->locator->locate($template);
         } catch (InvalidArgumentException $e) {
            return false;
         }

         return new FileStorage($file);
      }

      /**
       * Implement your check to see if request is made from mobile platform
       */
       private function isMobile($request)
       {
           return true;
       }
 }

As you can see this isn't the full solution, but I hope that this, at least, points you to the right direction.

EDIT: Just found out that there is a bundle with mobile detection capabilities, with custom twig engine that renders template file depending on a device that sent request ZenstruckMobileBundle, although I never used it so... :)

Saturday, October 22, 2022
2

Thanks to some of the posters above, there was some information from Form Theming, but it wasn't exactly enough to go along with so I had to do a little bit of digging on github.

According to the documentation, Symfony uses twig templates to render the relevant bits of a form and it's containing elements. These are just {% block %}s in twig. So the first step was to find where a select button is rendered within the symfony codebase.

Form Theming

Firstly, you create your own theme block in it's own twig file and you apply this theme to your form with the following code:

{% form_theme my_form_name 'form/file_to_overridewith.html.twig %}

So if I had overridden {% block form_row %} in the file above, then when I called {{ form_row(form) }} it would use my block instead of Symfony's default block.

Important: You don't have to override everything. Just override the things you want to change and Symfony will fall back to it's own block if it doesn't find one in your theme.

The Sourcecode

On github I found the source code for Symfony's "choice widget". It's a little complex but if you follow it through and experiment a little bit you'll see where it goes.

Within the choice_widget_collapsed block, I changed the select to uls and options to lis. Here's the theme file I created, note the minor differences described above:

{# Symfony renders a 'choice' or 'entity' field as a select dropdown - this changes it to ul/li's for our own CSS #}

{%- block choice_widget_collapsed -%}
    {%- if required and empty_value is none and not empty_value_in_choices and not multiple -%}
        {% set required = false %}
    {%- endif -%}
    <ul {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}>
        {%- if preferred_choices|length > 0 -%}
            {% set options = preferred_choices %}
            {{- block('choice_widget_options') -}}
            {%- if choices|length > 0 and separator is not none -%}
                <li disabled="disabled">{{ separator }}</li>
            {%- endif -%}
        {%- endif -%}
        {%- set options = choices -%}
        {{- block('choice_widget_options') -}}
    </ul>
{%- endblock choice_widget_collapsed -%}

{%- block choice_widget_options -%}
    {% for group_label, choice in options %}
        {%- if choice is iterable -%}
            <optgroup label="{{ group_label|trans({}, translation_domain) }}">
                {% set options = choice %}
                {{- block('choice_widget_options') -}}
            </optgroup>
        {%- else -%}
            <li value="{{ choice.value }}"{% if choice is selectedchoice(value) %} selected="selected"{% endif %}><a href="#">{{ choice.label|trans({}, translation_domain) }}</a></li>
        {%- endif -%}
    {% endfor %}
{%- endblock choice_widget_options -%}

Rendering

Now I can render my form with the following:

{{ form_widget(form.locator, {'attr': {'class': 'dropdown-menu'}}) }}

This uses my theme for the choice dropdown which contains ul and li tags instead of select and option ones. Pretty simple once you know where to look for the original code! The rendered HTML:

<ul id="elementtype_locator" name="elementtype[locator]" required="required" class="dropdown-menu">
    <li value="1"><a href="#">id</a></li>
    <li value="2"><a href="#">name</a></li>
    <li value="3"><a href="#">xpath</a></li>
</ul>

I also had to remove one of the lines that put 'Locator' at the top of the dropdown as there were four dropdown choices (including the empty_data one) instead of three.

Tuesday, October 4, 2022
5

What you want to use is the Twig {% flush %} command which is the equivalent of the PHP function flush(). Place it right after the </head> tag in your template for it to send the current buffer to the browser. Each time you call it it will output any output buffered so putting it in a loop will work fine.

For more info see the docs.

Friday, November 11, 2022
 
2
if (navigator.userAgent.match(/iPad/i) != null){ // may need changing?
  var js = document.createElement('script');
  js.type = "text/javascript";
  js.src = "/s/jquery.dropkick-1.0.0.js";

  var css = document.createElement('link');
  css.type = "text/css";
  css.rel = "stylesheet";
  css.href = "/c/dropkick.css";

  var h = document.getElementsByTagName('head')[0];
  h.appendChild(js);
  h.appendChild(css);
}

Or whatever would be in the User-Agent header for an iPad.

References:

  • window.navigator.userAgent
  • document.createElement
  • node.appendChild
Friday, September 9, 2022
 
4

The '/api' part of the proxy_pass target is the URI part the error message is referring to. Since ifs are pseudo-locations, and proxy_pass with a uri part replaces the matched location with the given uri, it's not allowed in an if. If you just invert that if's logic, you can get this to work:

location /tvoice {
  if ($http_user_agent ~ iPhone ) {
    # return 301 is preferable to a rewrite when you're not actually rewriting anything
    return 301 https://m.domain1.com$request_uri;

    # if you're on an older version of nginx that doesn't support the above syntax,
    # this rewrite is preferred over your original one:
    # rewrite ^ https://m.domain.com$request_uri? permanent;
  }

  ...

  if ($http_user_agent !~ facebookexternalhit) {
    rewrite ^/tvoice/(.*) http://mydomain.com/#!tvoice/$1 permanent;
  }

  proxy_pass         http://mydomain.com/api;
}
Saturday, November 26, 2022
 
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :