Ajax template: how to handle head section

Ok, you want that mobile devices always load mobile templates. Desktop devices load template files based on resolution: if < 1080 mobile ones, > 1080 desktop ones.

Your workflow should be:

  1. On init you check for mobile devices using wp_is_mobile. If true you add a template filter that returns str_replace('.php', '-mobile.php', $template); where $template is the original template required. The ‘front-page-mobile.php’ (or ‘page-mobile.php’ and so on) shows the content for mobile devices. And for what regard mobile devices you are done.
  2. if wp_is_mobile is true enqueue the mobile styles and scripts, if false enqueue the desktop styles and scripts. Desktop scripts have to include enquire.js and a script with your enquire.register and ajax stuff because you need them only for desktops.
  3. Your custom js script should send an ajax request every time resolution change from 1080 + to 1080 – and viceversa. Resolution change is handled with enquire.js. The ajax request must be sended to the same url from witch is called. Remember that an ajax call is just a normal http request so sending to the same url we init wp, run the query related to the url, include a template file and obtain the html for the page. Of course from ajax we need only the body content, but using a trick getting only body is easy.

I create a plugin in a class, for 2 reasons: ability to use static variables, and ability to use short functions name avoiding collisions.

Note that code is untested and you have to intend it as a proof of concept.

<?php
/**
 * Plugin Name: GM Mobile Workflow
 * Plugin URI: http://wordpress.stackexchange.com/questions/111751/ajax-template-how-to-handle-head-section/
 * Description: A Desktop / Mobile Workflow for WPSE. 
 * Author: G.M.
 * Author URI: http://wordpress.stackexchange.com/users/35541/g-m
 */

class MyMobileWorkflow {

  static $ismobile = false;

  static $desktop_template="desktop";

  static $isajax = false;

  static function init() {
    self::$isajax = ( ! empty($_SERVER['HTTP_X_REQUESTED_WITH']) && ( strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest' ) );
    add_action('init', array( __CLASS__, 'check'), 1 );
    add_action('wp_loaded', array( __CLASS__, 'add_assets') );
    if ( ! self::$isajax ) add_filter('template_include', array( __CLASS__, 'filter_template') );
    if ( self::$isajax ) self::$desktop_template = isset($_POST['template']) && $_POST['template'] ? $_POST['template'] : 'desktop';
    if ( ! in_array( self::$desktop_template, array('desktop', 'mobile') ) ) self::$desktop_template="desktop";
  }

  static function check() {
    if ( ! isset($_POST['skip_server_check']) || ! $_POST['skip_server_check'] ) {
      self::$ismobile = wp_is_mobile();
    }
  }

  static function filter_template( $template ) {
    if ( ! self::$isajax && self::$ismobile ) return str_replace('.php', '-mobile.php', $template);
    return $template;
  }

  static function add_assets( ) {
    add_action('wp_enqueue_scripts', array( __CLASS__, 'add_core_assets') );
    if ( self::$ismobile ) {
      add_action('wp_enqueue_scripts', array( __CLASS__, 'add_mobile_assets') );
    } else {
      add_action('wp_enqueue_scripts', array( __CLASS__, 'add_desktop_assets') );
    }
  }

  static function add_core_assets () {
    wp_enqueue_style('reset', plugins_url('/css/reset.css', __FILE__) );
    wp_enqueue_style('main', plugins_url('/css/style.css', __FILE__));
  }

  static function add_mobile_assets ( $from_desktop = false ) {
    wp_enqueue_style('mobile', plugins_url('/css/style-mobile.css', __FILE__), array('main'), null );
    $deps = $from_desktop ? array('spk_slide') : array();
    wp_enqueue_style('videojs-css', plugins_url('/js/video-js.css', __FILE__), $deps, null );
    wp_enqueue_script('videojs-js', plugins_url('/js/videojs/video.js', __FILE__), $deps, null );
  }

  static function add_desktop_assets () {
    wp_enqueue_script( 'enquire',  plugins_url('/js/enquire.min.js', __FILE__), array('jquery'), null );
    wp_enqueue_script( 'jwplayer', plugins_url('/js/jwplayer.js', __FILE__), array('jquery'), null );
    wp_enqueue_script( 'bootstrap', plugins_url('/js/bootstrap.js', __FILE__), array('jquery'), null );
    wp_enqueue_script( 'spk_slide', plugins_url('/js/slides.js', __FILE__), array('jquery'), null );
    self::add_mobile_assets( true );
    wp_enqueue_script( 'MyMobileWorkflow', plugins_url('/js/MyMobileWorkflow.js', __FILE__), array('jquery', 'enquire'), null );
  }

}

add_action('after_setup_theme',  array('MyMobileWorkflow', 'init') );

I think code should be self-explanatory, and it implement essentially the 3 points workflow defined above.

I take some code from what you posted, only made a bit of order 🙂

Now when we open home url from a mobile device, thanks to a filter, wordpress check for the file front-page-mobile.php instead of front-page.php.

What should this template file contain? Something like this:

get_header();
get_template_part( 'homepage-mobile' );
get_footer('mobile');

So you have to prepare a header.php that must contain a wp_head() call that thanks to our conditional scripts enqueuing, will put on head only mobile scripts and styles.
Than you have to prepare a homepage-mobile.php containing the output for mobile devices and a file footer-mobile.php where put the footer for mobile devices that have to contain the wp_footer() call.

The trick for your header.php is that you should put before all the code something like:

<?php if ( MyMobileWorkflow::$isajax ) return; ?><!DOCTYPE html>
....
</head><body <?php body_class('desktop'); ?>>

In this way if the template is included from an ajax request it output nothing. Note that header.php have to end with the body tag open, so everithing is part of body go after header.php and is outputted on ajax call.
We added a class ‘desktop’ to our body that will be useful later.

For same reason footer.php and footer-mobile.php should both contain something like this:

<footer> ... </footer>
wp_footer();

<?php if ( ! MyMobileWorkflow::$isajax ) { ?>
</body></html>
<?php } ?>

In this way everthing is included from get_header() and get_footer() is the body content and is outputted on ajax requests.

Now the important thing is the front-page.php (or the other template files) that are required by url on desktop devices.

We know that in this case in the header, we will have jquery, enquire.js and your custom script. What the front-page.php should look like? Something like this:

get_header();
if ( MyMobileWorkflow::$desktop_template == 'mobile' ) {
  get_template_part( 'homepage-mobile' );
  get_footer('mobile');
} else {
  get_template_part( 'homepage-desktop' );
  get_footer();
}

So, our template file on normal (not ajax) requests from desktops will output the entire content of the desktop templates.

However, on ajax requests, thank to our header.php and footer.php tricks, our template return only the content that goes between <body> and </body>. Perfect!

After enquire.js recognize the desktop resolution, if needed (resolution is <= 1080) have to send an ajax request to load the mobile template.

So let’s write our custom js script (MyMobileWorkflow.js) for register the enquire.js breakpoints and the ajax calls. The code in this file should be something like:

(function($) {

MyMobileWorkflow = {}
MyMobileWorkflowCache = { desktop: "", mobile: "" }

MyMobileWorkflow.load_template = function( ismobile ) {
  var template = ismobile ? 'mobile' : 'desktop';
  if ( $('body').data('nowTemplate') && $('body').data('nowTemplate') == template ) return false;
  $('body').data('nowTemplate', template );
  if ( MyMobileWorkflowCache[template] ) {
      $('body').html(  MyMobileWorkflowCache[template] );
  } else {
      $('body').html('<span class="loading">Loading...</span>');
      $.ajax({
        url: window.location.href,
        type: 'POST',
        dataType: 'html',
        data: ( { skip_server_check : '1', template: template } ),
        success: function( htmlData ) {
           MyMobileWorkflowCache[template] = htmlData;
          $('body').html( htmlData );
        }
      });
  }
}

$().ready(function(e) {
  if ( $('body').hasClass('desktop') ) MyMobileWorkflowCache['desktop'] = $('body').html();
});

enquire.register("screen and (max-width:1080px)", {

  match : function() {
    $('body').removeClass('desktop');
    MyMobileWorkflow.load_template(true);
  },

  unmatch : function() {
      MyMobileWorkflow.load_template(false);
  }

});

})(jQuery);

What this script do?

Everytime resolution change from 1080+ to 1080- and viceversa the script search the value in a cache variable. If nothing is found send an ajax call to the current url e.g. http://site.com/some/page and pass some data: skip_server_check that makes our class not run wp_is_mobile on init; and a variable template setted to desktop or mobile that tells our template file to load (in case of home page) homepage-desktop.php or homepage-mobile.php respectively.

As we already know, being an ajax request, even if get_header() and get_footer() will be called, the template output only the body content. This body content is putted between <body> and </body> using jQuery.html().

After retrieving via ajax, the html output is stored in the cache variabe, so ajax is runned only one time. Also note that on document ready (because by default we load the desktop templates) the cache variable for desktop is filled with the current html content of body.

Note that homepage-mobile.php is the same file we use for mobile devices, so you have to write code one time for mobile devices and for < 1080px desktop screens.

homepage-desktop.php is the last file you have to write and must contain all code from <body> to </body> for > 1080px desktop screens.

Code I posted for templates handle only front page (front-page.php) but you have to implement same process for all 1st level templates you intend to use. (I call 1st level templates ones that are part of WP Template Hierarchy).

Try to limit number of them: front-page.php, index.php, page.php and single.php with some conditional tags in combination with get_template_part(), most of times do all the work for average site needs.

Again, remember code is untested and I usually do a lot of typos writing code here… 😉 But I think this should give you a direction.


Notice

The code posted here was edited a lot of times to solve bugs and typos, and also to accomplish some suggestion and feedbacks from OP and other users.
This final version take into account different aspects: SEO, performance, and so on, and – most important – seems to work, but of course, should be tested better and in a “real world” application.

Leave a Comment