Posts tagged with 'html5'

Making this blog work as an app on the iPhone (the wrap up)

So far in this series (one, two, three) we have specialized content, detection of phone orientation, basics of being Web Clip capable, and a back to home page functionality. This post wraps it all up by removing the latter, implementing some simple Back button functionality, a “loading” indicator, and I do a little bit of code housekeeping too.

Before we implement a back button, we need to implement a proper URL stack so that we can save where we’ve been in order that the Back button works properly. Since JavaScript’s arrays have push() and pop(), we might as well make use of an array (I’ve called it visitStack). It’s going to be a little peculiar though: we shall be pushing the page’s URL as we load it. That means when we use the Back button, the top of the stack will contain the current page, not the previous one. We shall have to pop off the current page’s URL, and then pop off the previous page’s URL in order to load it. The home page is “special” in that we don’t push it onto the stack: instead we shall use the fact that the stack is empty to signal that we’re moving back to the home page. The home page has some specific behavior – namely it doesn’t have a back button – that we want to trap.

Right. Now the Back button. I decided to use an image, rather than trying to implement it directly.

Back Button

I added it to the header div and gave it a name:

<img id="backbutton" alt="Back button" title="Back button" src="/somepath/backbutton.png" />

The CSS positions the button absolutely inside the area covered by the header div. I didn’t want to futz with floats, etc, trying to get it right, so I just placed it. It’s also described as display:none to begin with so that the code will display it as and when necessary.

#backbutton {display:none; position:absolute; top:27px; left:2px;}

OK, that’s it with the display semantics, time for some code. Here’s the code that shows and hides the button; it also adds/removes a click handler for the button:

  var showBackButton = function () {
    $("#backbutton").show().on("click", function (e) {
      var thisUrl = visitStack.pop();
      if (visitStack.length === 0) {
        loadPage("http://blog.boyet.com/", true);
      }
      else {
        var backUrl = visitStack.pop();
        loadPage(backUrl, false);
      }
    });
  };

  var hideBackButton = function () {
    $("#backbutton").off("click").hide();
  };

The hideBackButton() function is the simplest: it just removes the click handler and then hides the button. The showBackButton() function is a little more complex and we see visitStack in use. The code shows the button and then adds a click event handler to it. The event handler pops off the current page’s URL and then checks to see if the URL stack is empty. If it is, the home page is loaded. If it isn’t, the previous URL is popped off, and that URL is loaded. (Recall that the second parameter to loadPage() defines whether the URL defines the home page (true) or not (false).)

The rest of the stuff happens in a much changed loadPage() function. First of all I had to create a separate boolean (isOnHomePage) to define whether the current page is the home page or not (it’s set to true initially). Using this made it simple to detect the case where the user was navigating away from the home page to a secondary page and the Back button had to be displayed.

  var loadPage = function (url, isForHomePage) {
    window.scrollTo(0, 0);
    addProgressDiv();
    $("#wrapper").load(url + " #wrapper", function () {
      fixupLinks();

      if (isForHomePage) {
        hideBackButton();
        isOnHomePage = true;
      }
      else {
        if (isOnHomePage) {
          isOnHomePage = false;
          showBackButton();
        }
        visitStack.push(url);
      }
      removeProgressDiv();
    });
  };

So, taking it step by step: first we scroll the page to the top as before, we then display a progress div (I’ll get to that in a moment), and then we load the wrapper div. Compared with last time, I’ve moved everything inside the completion function for the load() function. Once the content has been loaded, the completion function fires. First thing to do is fix up all the anchor links like before. After that we get to the new stuff: if the URL just loaded was for the home page, we hide the Back button and set our flag to denote that we’re now showing the home page. If it wasn’t for the home page, but we’re in fact moving away from the home page, we show the Back button (and turn off the “on home page” flag). We then push the new page’s URL onto the stack. Finally, after all other processing, we remove the progress div.

A quick aside on the progress div: it’s just a div that gets positioned absolutely and that has some simple “Loading…” text. Here’s the CSS for it:

#progress {display:none; 
  position:absolute; top: 100px; left:60px; width:200px; margin: 0 auto; padding:20px 0;
  text-align:center; 
  font-size:20px;
  background-color: rgba(0,0,0,0.6); color:white; -webkit-border-radius:10px; }

And here’s the two functions that display it and hide it. Note that I’m adding the div dynamically, it’s not part of the original markup. I could equally well have done this with the Back button of course.

  var addProgressDiv = function () {
    $(document.body).append("<div id='progress'>Loading...</div>");
    $("#progress").show();
  };

  var removeProgressDiv = function () {
    $("#progress").hide().remove();
  };

And here is what it looks like (along with the Back button):

Progress div

Here is the current code as a whole, so you can better understand how it all fits together (there are a couple of tweaks elsewhere as well).

$(function () {
  var 
    canOrient = false,
    isOnHomePage = true,
    visitStack = [];

  var changeOrientation = function () {
    if (canOrient) {
      var currentOrientation = window.orientation;
      if ((currentOrientation === 0) || (currentOrientation === 180)) {
        $(document.body).css("width", 320);
        $("#progress").css("width", 200);
      }
      else {
        $(document.body).css("width", 480);
        $("#progress").css("width", 360);
      }
    }
  };

  var wireUpOrientationChange = function () {
    canOrient = window.orientation !== undefined;
    if (canOrient) {
      window.onorientationchange = changeOrientation;
    }
  };

  var addProgressDiv = function () {
    $(document.body).append("<div id='progress'>Loading...</div>");
    $("#progress").show();
  };

  var removeProgressDiv = function () {
    $("#progress").hide().remove();
  };

  var showBackButton = function () {
    $("#backbutton").show().on("click", function (e) {
      var thisUrl = visitStack.pop();
      if (visitStack.length === 0) {
        loadPage("http://blog.boyet.com/", true);
      }
      else {
        var backUrl = visitStack.pop();
        loadPage(backUrl, false);
      }
    });
  };

  var hideBackButton = function () {
    $("#backbutton").off("click").hide();
  };

  var updateContent = function () {
    changeOrientation();
    fixupLinks();
  };

  var loadPage = function (url, isForHomePage) {
    window.scrollTo(0, 0);
    addProgressDiv();
    $("#wrapper").load(url + " #wrapper", function () {
      updateContent();

      if (isForHomePage) {
        hideBackButton();
        isOnHomePage = true;
      }
      else {
        if (isOnHomePage) {
          isOnHomePage = false;
          showBackButton();
        }
        visitStack.push(url);
      }
      removeProgressDiv();
    });
  };

  var fixupLinks = function () {
    $("#wrapper a").on("click", function (ev) {
      var url = ev.target.href,
          re = /blog\.boyet\.com/i;
      if (url && url.match(re)) {
        ev.preventDefault();
        loadPage(url, false);
      }
    });
  };

  wireUpOrientationChange();
  updateContent();
});

Again, if you want to see how this all looks on your iPhone, you need to open up blog.boyet.com in Safari, touch the middle button in the button bar, select “Add to Home Screen”, give it a name (I use “Boyet blog” since it just fits), and hit Return. At that point, it’s ready to be used on your Home screen.

(Quick notes: the CanOrient flag was to help when I was using desktop Safari to debug the code – desktop Safari has an option whereby you can get it to pretend to be an iPhone, but of course there is no orientation feature. The progress div also participates in the orientation code: I widen it for the case the phone is in landscape mode. I also decided to grow/shrink the body element instead of futzing with the individual named divs.)

Now playing:
Digital Analog Band - The Blues
(from The Best)


Making this blog work as an app on the iPhone (part 3)

The story so far (one, two) is that I’ve simplified the content of this blog for viewing on the iPhone, I’ve added code that recognizes when the phone’s orientation is changed from portrait to landscape (and vice versa), and I’ve added the necessary elements to the head element to indicate that this site can be viewed as a Web Clip. With the current state of play though, the moment you touch on a link, Safari fires up to display the page.

iPhoneTo change this behavior we’re going to make use of the basic structure of my markup. In essence within my body element I have two divs: one named header, the other wrapper. What I shall do is, in effect, fix up each and every anchor element within the wrapper div to load the wrapper div of the requested URL. The header div will remain the same and will not change in appearance. Of course, once I’ve loaded a new wrapper div, I’ll have to change each anchor element to fix up its click behavior, just as before.

That’s the basics, but there are a couple of wrinkles. If an href attribute points to a page outside of my domain I want the old behavior to apply (that is, the URL should be loaded by Safari, not by this Web Clip). The other one is how to get back to the Home page if I click on a link – the Safari chrome with its Back button has gone.

The first part then is the fix up code. This is actually pretty straightforward with jQuery.

  var fixupLinks = function () {
    $("#wrapper a").on("click", function (ev) {
      var url = ev.target.href,
          re = /blog\.boyet\.com/i;
      if (url && url.match(re)) {
        ev.preventDefault();
        loadPage(url, false);
      }
    });
  };

So we get all the anchor links inside the wrapper div and to each we add a click event handler. (I’m using the new jQuery on() syntax here for a change – available from 1.7 onwards – I prefer the way it reads.) The event handler grabs the href attribute from the target element (which is an anchor element, obviously), and then checks to see if it is for blog.boyet.com using an pretty standard “ignore case” regular expression. If it is, the default behavior of the event is suppressed and a new function is called (loadPage) to load the URL.

So, the fun stuff happens in loadPage(). Let’s see that one now.

  var loadPage = function (url, isHomePage) {
    window.scrollTo(0,0);
    $("#wrapper").load(url + " #wrapper", fixupLinks);
    if (!isHomePage) {
      $("#header").one("click", function (e) {
        loadPage("http://blog.boyet.com/", true);
      });
    }
  };

This is a bit more complicated, but not overly so. We see that the second parameter is a boolean indicating whether the URL for for the home page or not. The reason, as we’ll see, is that we don’t want to have “Back button” functionality for the home page (“You’re on the home page, click here to return to the home page” doesn’t sound too well planned). The first thing that happens is that we scroll the page to the top. If we didn’t do this, when we load the new div, we’ll remain at the same relative window position which might, after all, be half way down the page. The user would much more prefer to be positioned at the top of the page after clicking a link rather than half way down.

Next we make use of jQuery’s load() function to load the URL. This is called on the selector we wish to use as the recipient of the new page (hence the wrapper div). The load() function has some special behavior: if we follow the URL by a space and a selector (so “http://example.com/something.html #container”), jQuery will just load the contents of that selector from the URL. In essence, what this code says is “replace the contents of wrapper with the contents of wrapper from this other page”. (There is some secondary behavior here as well: any script blocks in the replacement markup are ignored – which is also what we would like.) The second parameter to load() is a function to execute once the content has been loaded: in our case we want to fix up the anchor elements in the same manner as before.

After that all happens, if the new content is not for the home page, we shall make the header div clickable. I’m using a “one shot” click event handler here (note the function name is one()) so that I don’t have to explicitly unbind the click event handler when it’s used (in an earlier version of the code I didn’t do this and the header got more and more click event handlers tacked on, so that in the end I was firing five or six click events every time I touched the header). The event handler loads the home page and so the whole header acts as the site’s Back button.

The final thing to tie it all up is to make sure fixupLinks() is called when the home page is initially displayed. It’s is a simple matter to add this to a jQuery document.ready() function.

Looks very nice now, I must say. Except…

There is still an issue. It’s fairly obvious once you think about it: the “Back” functionality I implemented is a misnomer since it’s more of a “Go to home page” function. So if I have a post that refers to another, clicking the header on the second post returns to the home page and not to the first post. Yuk. In the next installment I shall have to build a URL stack so that the Back button (and I’ll have to have a real button methinks) works as you’d expect.

Album cover for The Fame MonsterNow playing:
Lady Gaga - Dance In The Dark
(from The Fame Monster)


 


Making this blog work as an app on the iPhone (part 2)

Now that we have a special web site that displays properly either in portrait or landscape mode, let’s make it a web application that we pin to the Home screen. Boiled down to its essence, this is easy: you bring the web page up in Safari, touch the middle button in the bar at the bottom of the screen, and then select the Add to Home Screen option.

Making a web clip

iOS selects a screenshot for the icon (bleugh!) and allows you to edit the caption for the icon. Once that’s done the web page will appear as a web application or Web Clip on some page of your Home screen. If this were all that there was to it, this would be a very short post, but luckily. . .

Of course the very first thing we should do is to define a cool icon for the Web Clip. None of that “screen snapshot” stuff for us. Fire up your favorite paint program and design a 57×57 PNG file. I came up with this one for blog.boyet.com:

Home icon for Web Clip

Yeah, so I’m not an artist. Sue me.

Now you have to specify this icon in your web pages. Add a link element to your head element, like this:

<link rel="apple-touch-icon" href="/somepath/HomeIcon.png"/>

You can also provide higher resolution images for the iPad and for the retina display on the iPhone 4+:

<link rel="apple-touch-icon" sizes="72x72" href="/somepath/HomeIcon-ipad.png"/>
<link rel="apple-touch-icon" sizes="114x114" href="/somepath/HomeIcon-iphone4.png"/>

Next up is to hide the Safari chrome: the address bar and button bar. This is done through another meta element within the head element, and needs to be set before the rest of the tags will work:

<meta name="apple-mobile-web-app-capable" content="yes" />

This will leave just the device’s status bar at the top of the screen. If you want to get rid of that too, tough. The closest you can get is to make it translucent – your content will appear behind it. It’s another meta element.

<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

I just turned it to black for mine (the other alternative is to leave it at default, or remove the meta tag altogether):

<meta name="apple-mobile-web-app-status-bar-style" content="black" />

The other tweakable knob is to specify a start-up or splash image. This defaults to a screenshot of the last time the Web Clip was used, which is a little disconcerting especially if there’s no internet connection (it’ll look like you can interact with the page, but it’s just an image). Basically provide a portrait 320×480 pixel image:

<link rel="apple-touch-startup-image" href="/somepath/splash.png">

Now if you create a Web Clip from the web page, you will get all of that new nice behavior. The web app will still play nice when you re-orient the device. But, and it’s a big but, if you click on a “read more…” link, Safari will fire up up and the new page will load there. Ouch. Not really a web app if it’s just one page.

Next time, we’ll look at solving that with a judicious bit of JavaScript and jQuery.

Album cover for Land of confusionNow playing:
Genesis - Land of confusion (extended Version)
(from Land of confusion)



Making this blog work as an app on the iPhone (part 1)

With the iPhone you easily view a web site in Safari, but you get the annoying bits of chrome at the top (the address bar) and at the bottom (the buttons). What if you wanted your site to occupy the whole of the screen? Well, you can pin the site to the Home screen and, provided that you make a few changes to the HTML, CSS, and JavaScript, you can make your site behave as if it were pretty much a native app.

iPhone close-upFirst of all, like it or not, you’ve got to write your HTML and CSS to serve the content properly on a small screen (320px across in portrait mode, 480px in landscape mode). For readers’ small mobile devices, I created a special abbreviated form of this blog’s home page that just displays the first 500 characters or so of the post rather than all of it. That way the user can read the summary and, if they are enticed by the promise of a good article, they can tap the “Read more…” link.

To style this abbreviated content I have, in essence, two containers: the first for the header (whose id is imaginatively called “header”), and the second for the rest of the page (with an id of “wrapper”). We’ll be making more use of these div containers in part 2.

<body>
  <div id="header">
    <h1><em>Algorithms for the Masses</em><br />by Julian M Bucknall</h1>
  </div>
	
  <div id="wrapper">
  
    <!-- content goes here -->

 </div><!--end of wrapper -->
</body>

The main reason for creating these divs is to style them when the user changes the orientation of the device from portrait to landscape. For the initial CSS I had this:

#header {width:320px;}
#wrapper {width:320px;}

Which are the defaults for portrait mode.

Now we hit the fun parts. The first bit is to suppress the natural tendency for Safari on iOS to increase the size of the font when you switch from portrait to landscape (in essence Safari zooms the content to fit). Since one of the things I wanted to do was to display the content properly in landscape mode across the screen, I had to suppress this default in CSS:

html, body {-webkit-text-size-adjust: none;}

This magic CSS style will suppress the default behavior of iOS Safari to increase the text size when the device is re-oriented. Essentially you would add it to any element style you want to: here I reset the entire body element.

Now we need to add some magic sauce to the HTML markup in the head element.

  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">

This line instructs the iOS Safari that we are going to be responsible for defining the width of the content (it will be equal to the device’s physical width), and that we will be suppressing the zoom feature. When you view “normal” web pages in iOS Safari you can pinch to shrink the page or spread your fingers to expand the display. This meta tag essentially tells Safari that the site knows what it is doing with regard to the content and that the pinch gesture is not necessary.

Now for some JavaScript (yes, all this magic couldn’t be done without some JavaScript). We want to make sure that the content restyles itself when the user re-orients the device form portrait to landscape. To do this we make use of the window.onorientationchange event and the window.orientation property value.

var changeOrientation = function () {
  currentOrientation = window.orientation;
  if ((currentOrientation === 0) || (currentOrientation === 180)) {
    $("#header").css("width", 320);
    $("#wrapper").css("width", 320);
  }
  else {
    $("#header").css("width", 480);
    $("#wrapper").css("width", 480);
  }
};

var wireUpOrientationChange = function () {
  changeOrientation();
  window.onorientationchange = changeOrientation;
};

First of all we have the wireUpOrientationChange function, called when the page is initially loaded (document ready in jQuery terms). This calls the changeOrientation method to initialize the correct orientation at the moment the page is initially displayed, and then sets the event handler to track the changes to the device’s orientation from then on.

The changeOrientation method does the real work. It read the current orientation and if it’s 0 or 180 it assumes portrait mode, and anything else (actually –90 or 90 – the orientation is a value in degrees) it assumes landscape mode. In either case it alters the width style of the CSS for both the header and wrapper divs appropriately. Note the use of jQuery here; yes, jQuery is being loaded by the overall HTML page.

These changes will be enough to make iOS Safari display the web site appropriately in either portrait or landscape mode. Next time we shall look at how to pin the site to the Home screen and what needs to happen to fake the “native app'” look and feel.

Now playing:
Hancock, Herbie - One Finger Snap
(from The Best of Herbie Hancock: The Blue Note Years)

HTML5 and validation issues with this blog

OK, I was nuts. If it ain’t broke, don’t fix it, right? But what the heck, it’s part of the technology stack I’m supposed to know and use and promote, and furthermore I have a text editor and know how to use it.

HTML5 logoThe issue is I’ve switched this blog to HTML5 markup rather than XHTML, which is what it was before. No sweat, you snort, the doctype is way simpler:

<!DOCTYPE html>

and you can get rid of that other XML namespace gubbins on the <html> tag:

<html lang="en">

Nice and easy. Morceau de gâteau. Yeah, that’s what I thought. Except when I then ran the W3 validator on it I got a rather bizarre error on the <html> tag: “application/xhtml+xml is not an appropriate Content-Type for a document whose root element is not in a namespace”. Whuuuuut?

Some searching later (and boy, was I getting ratty about it), I managed to piece together what was happening. So in the interests of getting this out there in some searchable fashion, here goes.

When you visit a website in a browser, you send along a request to the site and part of that request is the Accept header. Here’s what Firefox sends for the Accept header for the main page for my blog, as an example:

text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

What this says (roughly) is that Firefox will accept an HTML document, an XHTML document, or a plain XML document. All of the other browsers send something similar. IIS7 (the web server my hosting service uses) notes this and replies with a response that has the following Content-Type header:

text/html; charset=utf-8

Magic. “Hey, Firefox, got your request, and here’s an HTML document for you.” That’s supposed to be what happens. All is good.

Unfortunately for me, the W3 validator was sending a similar Accept header as part of the request, but IIS7 was deciding – all on its lonesome – that it was going to send back this Content-Type header instead:

application/xhtml+xml; charset=utf-8

At which point, the W3 validator was going to start complaining that the document it got wasn’t valid XHTML, and in particular there was no XML namespace declared in the <html> tag. Without that, it gave up trying to decipher the document (that is, the web page) because the markup failed the first validation question of all.

So why was IIS7 deciding that it should declare the document as containing XHTML or XML? Who knows. There’s an interesting blog post from some chap at Opera that seems to suggest that, absent any other clues, IIS7 does some user agent sniffing and makes some kind of decision based on that. Maybe it was doing the same with the W3 validator. That left me with a problem though. How could I fix this, given that my access to IIS7 on my hosting company is pretty thin?

Luckily, I use GraffitiCMS to serve this blog. GraffitiCMS is open source, so I can change it if needed, which is what I did. I opened up TemplatedThemePage.cs (all public-facing pages are of this type), and added this line of code in the OnLoad method in order to force the response’s Content-Type to HTML:

protected override void OnLoad(EventArgs e)
{
  base.OnLoad(e);

  Response.ContentType = "text/html"; // force HTML type

  Initialize();

  //...

No more was I going to allow IIS7 to decide for me. A quick recompile, and then I uploaded the DLLs to my site, and the W3 validator was satisfied that the content was HTML.

And then proceeded to detail a bazillion errors.

There are two big ones that I want to talk about right now. The first one is the Grooveshark widgets I’ve been using recently. In essence, they’re copy-and-paste object elements from Grooveshark, and they break W3’s HTML5 validation big time. I really can’t be bothered to work out the changes I would need to make every time I use them, so as soon as I have time to update each post, they’re gone.

The second one is a small bug in Windows Live Writer (and I’ve just reported it). For images, WLW adds a border attribute to the img element – specifically border="0" – and HTML5 has deprecated that particular value for that particular attribute, suggesting that you use CSS instead. Hence every image on my web pages produces a warning. The problem is there’s no way to alter the default HTML markup that WLW produces for an image, and so I will have to go in to the Source View to edit the markup for every blog post to remove the warnings. Oh joy. Looking forward to that when I have time, although it can be done with a simple search/replace.

There are some other small bugs (can’t have the clear attribute on a <br /> tag, etc), but they’re pretty easy to fix (an easy search/replace again).

After that I can start experimenting with the site’s templates to use header and footer elements, and so on; the whole panoply of HTML5. Stay tuned for more news on that.

Album cover for The Original SoundtrackNow playing:
10cc - Une Nuit a Paris: One Night in Paris, Pt. 1/The Same Night in Paris, Pt. 2
(from The Original Soundtrack)


Extras

Search

About Me

I'm Julian M Bucknall, the M because it's my middle initial and because I and the other Julian Bucknall (the movie guy) would like to differentiate ourselves.

I'm a programmer by trade, an actor by ambition, and an algorithms guy by osmosis. I write articles for PCPlus in my spare time, not that there's much of that.

Julian M Bucknall Apart from that, an ex-pat Brit, atheist, microbrew enthusiast, Pet Shop Boys fanboy, slide rule and HP calculator collector, amateur photographer, Altoids muncher.

DevExpress

I'm Chief Technology Officer at Developer Express, a software company that writes some great controls and tools for .NET and Delphi. I'm responsible for the technology oversight and vision of the company.

Validation

Validate markup as HTML5 (beta)     Validate CSS

Bottom swirl

Archives

May 2012 (4)
SMTWTFS
« Apr  
12345
6789101112
13141516171819
20212223242526
2728293031

Like this Archive Calendar widget? Download it here.

Social networking

The OUT Campaign

The OUT Campaign

My Tweets

  • Honest Movie Trailer of Phantom Menace http://t.co/sif8y4Ns and then Battleship, er, Transformers http://t.co/sif8y4Ns
  • Damn, Donna Summer and Chuck Brown both gone in the last 24 hours. Different types of music, sure, but enjoyed them both. :(
  • Just saw a company page showing a list of tweets with "Join the conversation" linked to their Twitter a/c. The tweets are 6 months old #fail
Bottom swirl