Wednesday 14 May 2008

Web 2.0 and a Searchable Website

Golf Adept is as much an application as a web site. To provide a consistent look and feel I have included Web 2.0 features from the front page on down - Panels, tabs, zoom, Ajax, JSON, etc. This raised two immediate problems to solve:

  1. Web 2.0 libraries are large to download. ExtJS, for example, is over 900kb uncompressed.
  2. A lot of the pages are invisible to the search engines. They are loaded by JSON and Ajax, then driven by clicks rather than links.
The first problem has traditional solutions. Extranious code can be excluded. I chose not to do that. Standard JavaScript compression (using jsmin) brings the size down to 500kb. Enabling gzip compression on the server reduced transfer to 155kb - a size I have considered acceptable. Broadband is not always that broad - and even 155kb can take some time. I like to give my customers something to read while waiting for the bells and whistles. Fortunately the solution for this is the same as for problem two...

Golf Adept is a Web 2.0 application - basically an empty page that is filled by JavaScript. There is nothing for the search engines to review and nothing for the visitor to read while the JavaScript code is being downloaded. Most of the relatively static pages are downloaded by Ajax to fill the tab frames on demand.

Django allows us to include HTML templates - and just like the Ajax payload downloaded to display it is a pure HTML fragment. I refactored my main Web 2.0 page into staticBase.html with main.html and a new staticPage.html extending it. Even the Web 2.0 main page loads header, home and footer as a static html template to display for the user to read while the JavaScript is loading.

The footer includes a site-map link that points to the staticPage template, giving it the name of the HTML fragments that are the contents of the tabs in the Web 2.0 implementation. The static page has the same layout and look as the main Web 2.0 pages. Where the tabs are is blank with a link to the Web 2.0 site.

In the end I have a Web 2.0 site that can still be walked by search engines - any by other systems that cannot use JavaScript. And all this is without duplication of content - thanks to Django template inheritance.

Hmmm, this has been a text-centric blog. I prefer to learn from example, so here goes:


staticBase.html


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>
{% block title %}{% endblock %}
{%if session.user%} for {{session.user.nickname|escape}}{%endif%}
</title>
{% block css %}{% endblock %}
{%if session.isDev%}
<link rel="stylesheet" type="text/css" href="/static/page/main.css" />
{%endif%}
{%if not session.isDev%}
<link rel="stylesheet" type="text/css" href="/static/all.css" />
{%endif%}
<link rel="stylesheet" type="text/css" "http://www.google.com/uds/solutions/dynamicfeed/gfdynamicfeedcontrol.css" />
<script type="text/javascript" src="/static/lib/dynamicFeeds.js"></script>
</head>
<body>
<div id="static-body">
<table style="width:760px;margin:0 auto;">
<tr><td style="height:380px;"></td></tr>
<tr><td style="height:30px;background-color:#EEEEFF;color:#BBB">
{% block loading-message %}Loading...{% endblock %}
</td></tr>
<tr><td>{% block body %}{% endblock %}</td></tr>
</table>
{%include "page/footer.html"%}

{% block end-load %}{% endblock %}

<!-- Afterwards so image loads after we have something to read -->
{%include "page/header.html"%}
</div>
</body>
</html>



main.html


{% extends "page/staticBase.html" %}

{% block title %}
Golf Adept
{% endblock %}

{% block css %}
<link rel="stylesheet" type="text/css" href="static/lib/ext/resources/css/ext-all.css" />
{#<link rel="stylesheet" type="text/css" href="static/page/main.css" />#}
{% endblock %}

{% block loading-message %}
Loading...
{% endblock %}

{% block body %}
{%include "tab/home.html"%}
{% endblock %}

{% block end-load %}
<!-- Now that we have something to read go ahead with the loading... -->
{%if session.isDev%}
<script type="text/javascript" src="static/lib/ext/adapter/ext/ext-base-debug.js"></script>
<script type="text/javascript" src="static/lib/ext/ext-all-debug.js"></script>
<script type="text/javascript" src="static/lib/Ext.ux.MaximizeTool.js"></script>
<script type="text/javascript" src="static/lib/Ext.ux.Plugin.RemoteComponent.js"></script>
<script type="text/javascript" src="static/lib/Ext.ux.IFrameComponent.js"></script>
<script type="text/javascript" src="static/lib/Ext.ux.layout.CenterLayout.js"></script>
<script type="text/javascript" src="static/lib/Ext.ux.StatefulTabPanel.js"></script>
<script type="text/javascript" src="static/lib/loadJS.js"></script>
<script type="text/javascript" src="static/page/main.js"></script>
{#<script type="text/javascript" src="static/lib/dynamicFeeds.js"></script>#}
{%endif%}
{%if not session.isDev%}
<script type="text/javascript" src="static/all.js.css"></script>
{%endif%}
<script type="text/javascript" src="active/page/main.js"></script>
{% endblock %}



staticPage.html


{% extends "page/staticBase.html" %}

{% block title %}
Golf Adept - {{ title }}
{% endblock %}

{% block loading-message %}
<a href="/">Home</a>
{% endblock %}

{% block body %}
{% include content %}
{% endblock %}



sitemap.html


<h3>Golf Adept Overview</h3>
<ul>
<li><a href="/">Golf Adept Home Page</a></li>
<li><a href="/html/tab/home.html">Know Your Game</a></li>
<li><a href="/html/tab/whatIsGolfAdept.html">What is Golf Adept</a></li>
<li><a href="/html/tab/walkthrough-thumbnails.html">Walkthroughs</a></li>
<li><a href="/html/tab/aboutUs.html">About Us</a></li>
</ul>



main.js


var homeTabs = [
{title: "Home",autoLoad:{url: "tab/home.html", params:"",scripts:true}},
{title: "What is Golf Adept?",autoLoad:{url: "tab/whatIsGolfAdept.html"}},
{title: "Walkthrough",plugins:[Ext.remoteComponent('static/tab/walkthroughs/layout.json')]},
{title: "About Us",autoLoad:{url: "tab/aboutUs.html"}}
];
var homeTabPanel = new Ext.ux.StatefulTabPanel({
id: 'home-tab-panel',
style: "margin: 0px auto 0px auto;",
width:760,
resizeTabs:true, // turn on tab resizing
minTabWidth: 115,
tabWidth:135,
enableTabScroll:true,
defaults: {autoScroll:true,layout:'fit'},
frame:true,
items:homeTabs,
activeTab: 0,
maximizable:true,
// Listener for Google Analitics
listeners: {
activate: function(tab) {trackPageView(tab.title);},
}
});

Monday 12 May 2008

ExtJS and Saving State

I wrote an Ajax/Web 2.0 framework before those names were bandied about. Unfortunately (for me) others have forged ahead. So, for the new project I decided to consider an existing system. After research and consideration I chose Ext-JS for my Web-2.0 Ajax front-end. Very nice to look at, lots of features, good examples, good API documentation and limited general documentation.

I knew I wanted to save state and restore it on the next visit. The first components I created had a 'stateful' option. As it happens I was luck as this defaults to 'true'. For the life of me I could not find an example I trusted.

Finally, after prompting, I looked at the source. Once you know how, it is easy. For my example I will save the tab to display in a tab panel. Because this is a common occurrence, I have created a new class based on TabPanel:


Ext.ux.StatefulTabPanel = Ext.extend(Ext.TabPanel, {
stateEvents: ['tabchange'],
getState: function() {return{tab:this.getActiveTab().id}},
applyState: function(state) {this.setActiveTab(state.tab);}
});

To work, the system requires a state manager - a way to save the data. The Ext people have already written one that saves state in cookies. If you want to save the state to the server a new manager is needed. In my case I am quite happy to use cookies so that the state is kept on the browser for that user and machine. I did want it to live longer than the default 1 day:
Ext.state.Manager.setProvider(
new Ext.state.CookieProvider({
expires: new Date(new Date().getTime()+(1000*60*60*24*365)), //1 year from now
}));

Put this code in the start of your man Ext.onReady function.

Monday 5 May 2008

A Beginning

I have chosen to write Golf Adept using technologies all totally new for me. I am hoping others will gain value if I record as I learn.

I am starting this blog a little late since this journey commenced late last year with some work on J2ME. Just when it was ready to port from simulator to phone, Google announced the Android Challenge. No change in direction, but a modification of priorities. January to March was porting and upgrading the J2ME system to run and look good on the more sophisticated Android platform.

I had originally decided to run the server side on Plone - and the first static site for the Challenge is on that platform. But, Google steps in again and introduces the Google App Engine. As an environment it is as I want it - and the benefits of running on the Google server over one limited server are too attractive to ignore.

So, I have spent April porting the static application to GAE before going back to getting the J2ME running again. My first technical article will be on pre-compressing static content to save on processing time.