Fast Migration of Regular Sites to Mobile using µ.Flow Library - µ.Flow package blog

Recommend this page to a friend!
  All package blogs All package blogs   µ.Flow µ.Flow   Blog µ.Flow package blog   RSS 1.0 feed RSS 2.0 feed   Blog Fast Migration of Reg...  
  Post a comment Post a comment   See comments See comments (2)   Trackbacks (0)  

Author:

Viewers: 127

Last month viewers: 2

Package: µ.Flow

Mobile Web applications require fast retrieval of pages using AJAX requests to provide a smooth experience.

However when you have already implemented a regular site with multiple pages, moving on to single page application approach can require a lot of work and compromise prior SEO (Search Engine Optimizations).

Read this article to learn about a simpler approach based on using HTML5 pushState and AJAX requires that requires minor changes to your site pages and provide a smooth user experience preserving your site past SEO efforts.




Loaded Article

Contents

Introduction

Main Goals

Client Side Code using AngularJS

The Client Side Details

Server Side Details

Conclusion


Introduction

In this article I will talk about "HTML5 Mobile Application", a topic with certainly many aspects. For now I would like to cover the following aspects:

- It is recommended for "mobile apps" so I will cover how can it load partial content asynchronously without reloading the whole page

- How do I asynchronously load single page apps covering SEO concerns

Well, this last topic is not so trivial now. Google expects Web sites that are modern and friendly to give them higher search result rankings.

On the other hand, I think Google often tries to force their own standards, like the issue of #! (hashbang) URLs and things like that, so you have to deal with there own ideas rather to use common sense.

So in short, my solution that want to present here may not be the ultimate solution, but how I think it should be implemented as a quick and easy workaround.

This library may be used for many other aspects that are out of the scope of this article.

Main Goals

As we all know, development of mobile apps is "in vogue" nowadays or it will become so common that many developers will fear to have to work twice as much to do the complete job.

Modern Single Page Apps may not be optimized for SEO (Search Eengine Optimization) purpose.

If you read modern books they often tell you to start your application thinking of it to be used by mobile users and then enhance it to work on desktop browsers.

Well, the reality is, most existing applications are developed as desktop browser app and now there is a need to update them to work as mobile applications.

In this blog article I would like to describe how I changed a desktop browser based app to an responsive SEO-friendly HTML5 Web App.

Client Side Code using AngularJS

AngularJS Directive
.directive('frdlAjaxLinkBoddystripped', ['$location', '$window', function($location, $window) {

  return {

    restrict: "A",

    controller: ['$element', '$attrs', '$timeout', '$compile', '$location', '$window', function($element, $attrs, $timeout, $compile, $location, $window) {

      /* $locationProvider.html5Mode( true ); 

      $element.text( htmlentities($element.text) );*/

      $element.on('click', function(ev) {

        if ('undefined' !== window.history.replaceState) {

          var el = ev.target;

          var url = el.getAttribute('href');

          var uHost = new frdl.Url(url).getHost();

          if ('' !== uHost && new frdl.Url().getHost() !== uHost) return true;

          ev.preventDefault();

          var dest = 'body';

          var destMeta = document.querySelector('meta[name="frdl-ajax-link-boddystripped.destination"]');

          if (null !== destMeta) {

            dest = destMeta.getAttribute('content');

          }

          $.ajax({

            url: url,

            /* crossDomain: ('www.webfan.de' !== new frdl.Url().getHost() && 'webfan.de' !== new frdl.Url().getHost()) ? false :true, */

            crossDomain: true,

            cache: true,

            headers: {
              'X-Requested-With': 'XMLHttpRequest'
                /*,

                                 'Origin' : new frdl.Url().getScheme() + '://' + new frdl.Url().getHost() */

              ,
              'X-Flow-Ajax-Link-Boddystripped': 'destination="' + dest + '";'

            },

            type: 'GET',

            dataType: 'html'

          })

          .done(function(html) {

            /* alert('System maintenance ' + dest + ' ' + html);*/

            $(dest).html(html);

            var OnUnload = function(e) {

              e.preventDefault();

              /*  e.stopPropagation(); */

              return false;

            };

            window.addEventListener('beforeunload', OnUnload);

            window.history.replaceState({}, 'Title', url);

            window.removeEventListener('beforeunload', OnUnload);

            setTimeout(function() {

              try {

                var newTitle = document.querySelector('meta[name="document.title"]').getAttribute('content');

              } catch (err) {

              }

              if ('undefined' !== typeof newTitle) $('title').html(newTitle);

              try {
                $(document).scrollTop(0);
              } catch (err) {

              }

              $(document).trigger('readystatechange');

              if (true === frdl.Device().isMobile || true === frdl.Device().isTablet) {

                $(dest).trigger('create');

              }

            }, 10);




            /* $location.url(url);

             $location.search(new frdl.Url(url).getParams()); */

          }).fail(function(jqXHR, textStatus) {

            console.error('Error: ' + url + ' ' + jqXHR.status + ' ' + textStatus);

            window.location.href = url;

          }).always(function() {



          });




          return false;

        } else {

          return true;

        }



      });

    }]

  };

}])

The Client Side Details

This is written in AngularJS but I am sure anyone can convert it to plain JavaScript easily.

What it does? It handles all link marked with the frdlAjaxLinkBoddystripped attribute. If the link href refers to a different or untrusted domain, no action is taken and the default event will be triggered when the link is clicked.

Otherwise the link is pushed to the HTML5 state history, so the click event will be prevented. Instead an AJAX request is sent to the URL in the link href to retrieve the content.

Now when a a link is clicked:

<a class = "site_menue_link" href="/" frdl-ajax-link-boddystripped = ""> Home </a>
Sending Ajax request with headers
headers: { 'X-Requested-With': 'XMLHttpRequest', 'X-Flow-Ajax-Link-Boddystripped': 'destination="' + dest + '";' }
A meta element sent by server that should change the page title.

Server Side Details

In the output handler of the server detects the request coming via AJAX checking the headers and transforms the HTML output. By default the AJAX request response only needs the HTML of the body.

ob_start( function( $content ) {
$cH = $parse_headers( $_SERVER, $headers ); if(isset( $headers[ 'X-Requested-With'] ) && 'XMLHttpRequest' === $headers[ 'X-Requested-With' ]) {
preg_match( "/<title>(.*)<\/title>/s", $content, $titleMatches); preg_match( "/<body>(.*)<\/body>/s", $content, $matches); //for cache GET: $_GET['_______X-Requested-With'] = 'XMLHttpRequest'; if(isset( $matches[1] )) $content=((isset( $titleMatches[1] )) ? '<meta name="document.title" content="' . strip_tags( $titleMatches[1] ).'" />' : '').$matches[1];
}
//... });
Furthermore the AJAX request header can pass a CSS selector to just request a smaller part of the page content:
$content_reduced = null;
if(defined( '_BLOG_DOMAINE_' ) 
&& isset( $headers[ 'X-Requested-With' ])
&& 'XMLHttpRequest' === $headers[ 'X-Requested-With' ]
&& isset($headers['X-Flow-Ajax-Link-Boddystripped'])) {
 $s = strip_tags( $headers[ 'X-Flow-Ajax-Link-Boddystripped' ]);
 $_GET['_______X-Flow-Ajax-Link-Boddystripped']=$s;
 $s = explode(';', $s);
 foreach($s as $p => $opt) {
   $o = explode('=', $opt, 2);
   if( 'destination' === $o[0]) {
     $selector = $o[1];
     $selector = trim( $selector, '" ');
     $selector = str_replace("'", '"', $selector);
     foreach( $html->find( $selector ) as $element)
     { 
      if(null === $content_reduced ) $content_reduced=''; 
       $content_reduced .= $element->innertext;
     }            
   }
 }
}
The HTML meta elements meta[name="frdl-ajax-link-boddystripped.destination"] content define the destination of the new content retrieved via AJAX that will be inserted in the page.

The browser script uses HTML5 window.histroy.replaceState to set the history with hash URLs. That means the new URLs can be used and they will, "clicked" synchronously next time, produce the absolutely same result as when using asynchronous AJAX requests,

Conclusion

Using this approach there is no no need to have a special mobile version of the site. There is also no need to use #! hashbang URLs  or ?_escaped_fragment_ requests, which could require a another server side strategy.

You just need to to use JavaScript to synchronize your page presentation with the server side pages output. You do not need to change the links URL schema of your existing pages.

If you liked this article or you have a question about this approach to migrate your site Web pages to fast AJAX based Web apps using HTML5 pushState, post a comment here.


You need to be a registered user or login to post a comment

25,349 JavaScript developers registered to the JS Classes site.
Be One of Us!

Login Immediately with your account on:

FacebookGmail
HotmailStackOverflow
GitHubYahoo


Comments:

1. Update changes - Till Wehowski (2016-10-17 13:02)
Removed dependency on angularJS... - 1 reply
Read the whole comment and replies



  Post a comment Post a comment   See comments See comments (2)   Trackbacks (0)  
  All package blogs All package blogs   µ.Flow µ.Flow   Blog µ.Flow package blog   RSS 1.0 feed RSS 2.0 feed   Blog Fast Migration of Reg...