Skip navigation
2011

Building on my first article in this series, we're now going to add bookmarkable URL's and back button support to our AJAX driven pages.

 

For our specific usecase, we want to render an AJAX region within the same page which includes an external page (via ui:include). Again, this is to keep the smooth native look and feel of sliding transitions.

 

Here, we mimic the same AJAX call which drives our main navigation (rich:panelMenu) with a4j:jsFunction.

 

<a4j:jsFunction name="handleHashChange" render="@form" oncomplete="slideTo('component-page')">
            <f:param name="demo" value="param1"/>
            <f:param name="sample" value="param2"/>
</a4j:jsFunction>

 

This says, when our JavaScript function "handleHashChange()" receives the 2 parameters (param1 and param2), we render the same form as in (part1) and call our transition.

 

Now that we have the JavaScript tie into the AJAX driven JSF page, we can use some quick code to give us bookmarkability and back button support.

 

function locationHashChanged() {
 if(location.hash === "#home"){
     slideTo('home-page')
 }else{
//let's see if we have an acceptable hash
     var hashArray = location.hash.split(':');
     var id;
     var sample;
     if(hashArray.length === 2){
//it's good so format it for our a4j:javascript function
         id = hashArray[0].replace('#','');
         sample = hashArray[1];
//call the a4j:javascript method
         handleHashChange(id,sample);
     }
 }
}

//every time the app changes the hash, call the method above 
window.onhashchange = locationHashChanged;

window.addEventListener('load', function(e) {
if(location.hash == ''){
//it's the initial load, so let's go home
     slideTo('home-page');
     location.hash='home';
}else{
//page has been bookmarked, so take em there 
    locationHashChanged();
}
}, false);
         

 

Now, every time the hash changes on an AJAX call it goes into our mobile page history. So from our original navigation handler rich:panelMenu, we would do the following "location.hash=" after the AJAX call completes:

 

<rich:panelMenuItem oncomplete="location.hash='#{d.id}:#{demoNavigator.currentSample.id}'" render="@form" ...

 

That's it!

The user can hit the back button, refresh, or bookmark the page and the correct markup will be rendered.

You will see this code in action in the upcoming RichFaces Mobile Showcase.

I'm extremely stoked to announce that the JBoss Portlet Bridge is passing the JSR-329 TCK and has been approved by Oracle. This has been a challenging and fun ride down the road of standards due to the fact that this spec covers 2 others, JSR-286 (portlet 2.0) and JSR-314 (JSF 1.2).

 

I would like to personally thank the people who made this project a success:

The JBoss Community - Without the constant feedback and support, we would not have made it to this point.

Alexander Smirnov - The wizard behind the curtain, founder of Ajax4JSF, and overall brilliant guy.

Michael Freedman - Whose tireless effort towards making portals a better place with JSF is greatly appreciated. Mike's knowledge and dedication to the spec had been awesome to witness. Here's to JSF 2.0 in portals!

 

My excitement for this day and the future of the JBoss Portlet Bridge can be best compared to this youtube video about double rainbows (this is how I felt after passing all the tests)...

 

If you would like to run this TCK against our implementation, here are the instructions:

  1. Download and locally build the JBoss Portlet Bridge 2.1.1.CR1
  2. Download Pluto 2.0.0
  3. Checkout the JSR-329 TCK
  4. Run the TestSuite and build the TestHarness wars
    1. mvn clean install -DincludeJSF=mojarra -Dmojarra.version=1.2_13 -Dtck.generate-war=pluto2.0 -DincludeBridge -Dportlet-bridge.groupId=org.jboss.portletbridge -Dportlet-bridge.api.artifactId=portletbridge-api -Dportlet-bridge.impl.artifactId=portletbridge-impl -Dportlet-bridge.version=2.1.1.CR1
  5. Deploy the 3 wars to your pluto 2.0 portal:
    1. portlet-bridge-tck-main-jsr329-1.0.0.war

    2. portlet-bridge-tck-section3-2-lifecycle-set-jsr329-1.0.0.war

    3. portlet-bridge-tck-section6-2-configured-response-wrapper-jsr329-1.0.0.war

  6. Start the server and ensure all 211 pages deployed (http://localhost:8080/pluto)
  7. Stop the server and download the attachments from this Jira issue and put them in the appropriate spot as declared by the TCK User Guide.
  8. Start the server and issue the following command to run the TestHarness (replace paths with your environment paths):
    1. mvn surefire-report:report -Dtck.external-server=run-test -Dbridge.tck.test.file=/Users/wesleyhales/projects/JSRs/jsr329/trunk_2.0.x/test.xml -Dbridge.tck.login-file=/Users/wesleyhales/projects/JSRs/jsr329/trunk_2.0.x/login.properties -Dbridge.tck.browser="*firefox /Applications/Firefox3.6.app/Contents/MacOS/firefox-bin"
  9. Some tests will fail, this is normal and noted in the TCK User Guide from Oracle. You must run the failed tests individually. See the test.xml file for notes and instructions on running the individual tests.
  10. If you have any issues, review the PBR-254 jira for anything you may have missed.

I thought it would be a good idea if I document the problems as I run into them. Starting with a problem within the JSF 2 spec (http://java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-1024), you cannot have something like:

 

<h:form>
   <f:ajax .. render="some-panel-id"/>
<h:form>

<a4j:outputPanel id="some-panel-id">
   <ui:include src="#{a dynamically generated URI}" />
</a4j:outputPanel>

 

If the ui:include contains another h:form, it will not work on the first request. But it will work on the second one This is because the first render does not include the hidden javax.faces.ViewState input.

 

To resolve this issue, it was fairly simple. Take the code from above and note the changes:

 

<h:form>
   <f:ajax .. render="@form"/>

   <ui:include src="#{a dynamically generated URI}" />

<h:form>

 

So remove the outputPanel and allow the form from the include to be nested. This gives us a ViewState for the form(s) on first request.

 

Why would you want an ui:include within the same markup as your navigation? The main reason is because the "mobile web way" is to put all your content within one page. This allows you to use divs as mock pages and gives you the smooth sliding transitions without an HTTP request. With RichFaces 4, you can just render these regions on ajax requests - thus giving you an easy way to take an existing site to the mobile web.

Let's talk about a very experimental approach for loading images. Every external file request is expensive over a 3G connection. Sure, things are getting better and speeds are improving, but 3G will be the IE6 of the mobile era.
During the writing of TweetStream, a mobile app which aggregates twitter - filtering out tweets for certain hashtags, I found that each tweet’s user icon was costly when loading the app. You can’t view a tweet without an image next to it, so it wouldn’t feel right without loading the image. The problem was with one image out of the 100’s that were being loaded. It took this one image over a second to load.
Seeing the problem and feeling helpless about the bottleneck, I came up with the following prototype. The only problem is that CORS (Cross-Origin Resource Sharing) served images are new and not served up by many public image services...yet. Luckily, Google is doing it with Picasa images and I’m not sure who else is, so please comment if you know of any others.
The concept is simple - load the image via Javascript, draw it into a canvas tag, base64 the image data, store it into localStorage, and return the base64 url back to the DOM when offline or on a slow connection. In conjunction with the applicationCache you would have the ability to create a ass kicking off line mobile application. localStorage is limited to 5MB, but it still gives you an option that isn’t available out of the box today.

 

In this code, we provide an image URL and base64 it via the canvas tag. Note the use of “crossOrigin” attribute. This is a feature recently provided in the Webkit nightly build.

function cacheExternalImage(url) {
    var img = new Image(); // width, height values are optional params
    //http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
    //remote server has to support CORS
    img.crossOrigin = '';
    img.src = url;
    img.onload = function() {
        if(img.complete){
            //this is where you could proxy server side
            load(img);
        }
    }
    var canvas = document.createElement("canvas");
    canvas.width = img.width;
    canvas.height = img.height;


    // Copy the image contents to the canvas
    var ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0,0);
    img.src = ctx.canvas.toDataURL("image/png");
    return img
}


Again, this is very experimental and still needs some polish. If you had to use external images that are not being served up with CORS headers, you can proxy the image server side and use the same approach making the image seem local. It would probably be even more efficient to do the base64 server side in this case.
This is just a proposal and not a production solution. Let us know if you have had a better experience.

I'm extremely excited to be speaking to and mingling with mobile/HTML5 related developers over the coming months. I will be speaking at the following events...

 

 

  • HTML5 User Group - Sept 22nd - Atlanta Ga.
    http://www.meetup.com/AtlantaHTML5/events/29823121/
    The Mobile Web - HTML5 on Mobile Devices
    About the talk:
    HTML5 is the new hotness and so is the "Mobile Web". In this kick-off meeting of the Atlanta HTML5 User Group, Wesley will discuss topics such as hardware acceleration by way of the device GPU, Caching via localStorage, client side Profiling tools, and overall performance tips and techniques when using HTML5, CSS3, and Javascript.

 

  • JavaOne 2011 - October 2-6 - San Francisco Ca.
    https://oracleus.wingateweb.com/scheduler/modifySession.do?SESSION_ID=19080
    Going Mobile with Java-Based Technologies Today
    ...Attendees will learn which front-end mobile frameworks work best with Java-based technologies and how they can be used to kick-start your own applications. The presentation begins with an overview of technologies used to create the demo and then jumps into the code for a step-by-step tutorial.



If you're in Atlanta, I highly encourage you to go ahead and sign up for the first 2 events if you're interested in HTML5. The User Group meeting caps out at 68 people and we are already at 46 RSVPs. We've received amazing support and response from the Atlanta community for these events!

And as for JavaOne, I'm looking forward to speaking on how we can make the mobile web faster with JBoss middleware!

 

If you have any questions, please ask!

Wesley