2

I know it's easy stuff, but I'm new to this and I can't understand which is the best approach.

Background

The template I'm working on is a dual theme Desktop/Mobile, UA Sniffing based. Now I just added responsiveness through enquire.js and ajax and everithing changed: I'm struggling in getting things to work properly, is the first time I'm dealing with ajax.

Scenario

My template is dynamically loaded through ajax in fact if you try to resize the width of the window below 1080px, the mobile template will show up. (it will show up on every mobile device too but this is not important for us atm)

So, responsiveness has been implemented with the help of enquire.js and ajax calls (see code below).

Originally, the template was static so at the moment, the whole section is still conditionally loaded through if statements in functions.php. (e.g. the video script should just load on certain pages of the desktop version)

Issues

  1. The mobile template -which has been styled with the mobile.css stylesheet- doesn't seem to have any effect. Should I change the way stylesheets and scripts are loaded due to the new ajax/enquire thing?
  2. the content i.e. the_content(), doesn't show up. Why? And how to load it in my scenario? found! -- document.write(data) was owerwriting everything! I added $("body").append(data); instead

Follows the code

functions.php

    //Load Stylesheet
function add_desktop_styles() {
    wp_register_style('reset', get_template_directory_uri().'/reset.css');

    wp_register_style('style', get_template_directory_uri().'/style.css', array('reset') );
    wp_enqueue_style('style');

    //$mobile = mobile_device_detect();
    //if ($mobile==true) {
    if (wp_is_mobile()) {
        wp_register_style('mobile', get_template_directory_uri().'/mobile.css', array('reset') );
        wp_enqueue_style('mobile');
    }
}
add_action('wp_head', 'add_desktop_styles', '1');



//UA Sniffing
function devicecontrol() {
        require_once('_/inc/mobile_device_detect.php');
}
add_action('wp_loaded', 'devicecontrol', '2');



//AJAX
function your_function_name() {
    wp_enqueue_script( 'function', get_template_directory_uri().'/_/js/my_js_stuff.js', array('jquery','enquire'), true);
    wp_localize_script( 'function', 'my_ajax_script', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ) ) );
}
add_action('template_redirect', 'your_function_name');

function get_mobile_template()
{
    include('templates/pages/homepage-phone.php');
    die();
}
add_action("wp_ajax_nopriv_get_mobile_template", "get_mobile_template");
add_action("wp_ajax_get_mobile_template", "get_mobile_template");

function get_desktop_template()
{
    if (!wp_is_mobile()) {
        include('templates/pages/homepage-computer.php');
    } else {
        include('templates/pages/homepage-phone.php');
    }
    die();
}
add_action("wp_ajax_nopriv_get_desktop_template", "get_desktop_template");
add_action("wp_ajax_get_desktop_template", "get_desktop_template");



//jQuery
if ( !function_exists( 'core_mods' ) ) {
    function core_mods() {
        if ( !is_admin() ) {
            wp_deregister_script( 'jquery' );
            wp_register_script( 'jquery', ( "//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" ), false);
            wp_enqueue_script( 'jquery' );
        }
    }
    add_action( 'wp_enqueue_scripts', 'core_mods','2' );
}



//Scripts Mobile
function add_mobile_scripts() {
    wp_register_style('video-css', get_template_directory_uri().'/_/js/videojs/video-js.css');
    wp_enqueue_style('video-css');
    wp_register_script( 'video-js', get_template_directory_uri().'/_/js/videojs/video.js', null, false );
    wp_enqueue_script( 'video-js' );
}
function isMobile() {
    //$mobile = mobile_device_detect();
    ///if ($mobile==true)
    if (wp_is_mobile()) {
        add_mobile_scripts();
    }
}
add_action( 'wp_enqueue_scripts', 'isMobile', '1' );



//Scripts Desktop
function addSlide() {
    /*wp_register_script( 'modernizr', get_template_directory_uri().'/_/js/modernizr.js', null, false );
    wp_enqueue_script( 'modernizr' );*/
    wp_register_script( 'enquire', get_template_directory_uri().'/_/js/enquire.min.js', null, false );
    wp_enqueue_script( 'enquire' );
    wp_register_script( 'jwplayer', get_template_directory_uri().'/_/js/jwplayer.js', null, false );
    wp_enqueue_script( 'jwplayer' );
    wp_register_script( 'bootstrap', get_template_directory_uri().'/_/js/bootstrap.js', array('jquery'), false );
    wp_enqueue_script( 'bootstrap' );
    wp_register_script( 'spk_slide', get_template_directory_uri().'/_/js/slides.js', array('jquery'), false );
    wp_enqueue_script( 'spk_slide' );
}



// Slider just on front page
function isSlideshowPage() {
  if ( is_front_page()
    || is_page('Bankkaufmann')
    || is_page('Hochschulabsolvent')
    || is_page('Professional')
    || is_page('Die Prüfungsstellen')
    || is_page('Von Beruf Verbandsprüfer')) {
        addSlide();
    }
}
add_action( 'wp_enqueue_scripts', 'isSlideshowPage' );

Js script

this script at the moment loads on all pages, I will wrap it and call it from the page-template later

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

    // OPTIONAL
    // If supplied, triggered when a media query matches.
    match : function() {
        jQuery.ajax({
            url: my_ajax_script.ajaxurl,
            data: ({action : 'get_mobile_template'}),
            success: function(data) {
            document.write(data);
            }
        });
    },

    unmatch : function() {$("body").empty();},

    // OPTIONAL
    // If supplied, triggered once, when the handler is registered.
    setup : function() {},

    // OPTIONAL, defaults to false
    // If set to true, defers execution of the setup function
    // until the first time the media query is matched
    deferSetup : true,

    // OPTIONAL
    // If supplied, triggered when handler is unregistered.
    // Place cleanup code here
    destroy : function() {}

});

enquire.register("screen and (min-width:1081px)", {

    // OPTIONAL
    // If supplied, triggered when a media query matches.
    match : function() {
        jQuery.ajax({
            url: my_ajax_script.ajaxurl,
            data: ({action : 'get_desktop_template'}),
            success: function(data) {
            document.write(data);
            }
        });
    },

    unmatch : function() {$("body").empty();},

    // OPTIONAL
    // If supplied, triggered once, when the handler is registered.
    setup : function() {},

    // OPTIONAL, defaults to false
    // If set to true, defers execution of the setup function
    // until the first time the media query is matched
    deferSetup : true,

    // OPTIONAL
    // If supplied, triggered when handler is unregistered.
    // Place cleanup code here
    destroy : function() {}

});
7
  • 2
    The important bit to grasp, which is not specific to WordPress, is that a normal request for a page and your subsequent AJAX request from that page are two entirely separate requests with no state maintained between the two. When you make the AJAX request, there is no query run on that request, so there's nothing for your templates to output.
    – Milo
    Aug 26, 2013 at 23:47
  • 2
    Instead to send ajax request to the admin-ajax.php send the ajax request to the same url (using js window.location.href ) but including an endpoind that can be 'mobile' or 'desktop'. An hook into template_include filter will do the rest. Hope it's clear.
    – gmazzap
    Aug 27, 2013 at 1:20
  • @G.M. : your solution is way more elegant than mine. I just don't understand which url you mean with "the same url" Aug 27, 2013 at 7:20
  • 1
    It's probably worth noting a caveat with this approach in WordPress- every page load will result in querying the database twice. when you make the initial non-ajax request, the database is queried for content that ultimately never gets output to the page. you might want to look into some way to stop the main query from happening on those page loads, if that's possible, or at least employ some caching to reduce server load.
    – Milo
    Aug 27, 2013 at 21:55
  • @Milo I'd like having your opinion on this: you said when you make the initial non-ajax request, the database is queried for content that ultimately never gets output to the page absolutely true. In chat I warned OP for that. But when try to write a code I undersand that <head> is outputted on first request and so SEO stuff (title, description) and all the plugings that alter head need a query is runned to work properly. So I thinked that to stop main query is not useful.. So, at least employ some caching, as you said, I think can be very useful. Any idea on how implement?
    – gmazzap
    Aug 28, 2013 at 4:27

1 Answer 1

6

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.

3
  • Wow this is much more that I expected and it sounds way cooler than other patch-style solutions I had in mind. Thank you Giuseppe I'm going to try to implement this right now Aug 27, 2013 at 20:27
  • Yesterday I found another bug in my version which now works somehow. I will try to implement this great solutin in the next 2 hours, will surely come back with feedback soon. Aug 28, 2013 at 6:55
  • The MyMobileWorkflow() in functions.php works well but when it comes to ajax if you resize the window everything becomes extremely slow. To better understand what I mean see goo.gl/BSnAEZ. Just a couple of minor fixes that could improve your answer: in the ajax call, "data" was missing: success: function(data) {. In front-page.php the mobile template was not loaded so I slightly modified your code but I'm not so sure if it's right (atm it just works) codepad.org/J9kf63rE Aug 28, 2013 at 21:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.