Last week I finally got to play with jQTouch, a jQuery based framework for building mobile-ready web sites. It was... rough to say the least. I'm very happy with the final result, but it took a bit of work to figure out how jQTouch wanted to work and what I should be doing with it. What follows are some basic notes, discoveries, and tips. I warn you though - most likely some of what follows could be a misunderstanding on my part.
- First and foremost, jQTouch is really meant for web applications that are menu driven. When designing your application, you want to think in terms of menus and sections. Your primary UI will be menus and forms.
- jQTouch works by convention. By using a button with a certain class, you build a back button. By using a link, you implicitly create an Ajax request. None of this was very clear to me at all.
- The demos, for the most part, work around the idea of one file. The file contains all the menus and "pages" for the application. I don't think this is very realistic. A simple application can do this, but anything dynamic and of intermediate complexity will want separate files.
- Given the above, here is a very basic, but complete template.
<script> $.jQTouch({ icon: 'jqtouch.png', statusBar: 'black-translucent' }); </script> </head> <body> <div id="home"> <div class="toolbar"><h1>Your App</h1></div> <ul class="rounded"> <li class="arrow"><a href="#about">About</a></li> <li class="arrow"><a href="#unicorn">Unicorn</a></li> </ul> </div> <div id="about"> <div class="toolbar"> <h1>About</h1> <a class="button cancel" href="#">Return</a> </div> <p> Hello World </p> </div> <div id="unicorn"> <div class="toolbar"> <h1>Unicorn</h1> <a class="button cancel" href="#">Return</a> </div> <p> Hello, Unicorns </p> </div> </body> </html><html> <head> <script src="js/jquery.js"></script> <script src="js/jqtouch.min.js" type="application/x-javascript" charset="utf-8"></script> <style type="text/css" media="screen">@import "css/jqtouch.min.css";</style> <style type="text/css" media="screen">@import "themes/jqt/theme.min.css";</style>
From top to bottom, here is what you need:
- Need to load jQuery and the jQTouch JS library.
- Need to load the JQTouch CSS, and one theme file. jQTouch ships with two themes.
- You need to startup jQTouch. There are many initialization options (detailed here).
- Now for the important part. Your "home" DIV is your home page. It consists of a toolbar and a set of links. Those classes are recognized by jQTouch and help generate the UI. Notice the links! They are both anchor links. This means they are expected to be within the file itself.
- Scrolling down, you see two blocks that match up with the anchor links above. Once again each one has a toolbar.
That's basically it for a simple app. Here is a screen shot of the home page:
And here is a shot of one of the "pages":
Because of the CSS conventions we used, that's all you have to do. Clicking the menu link loads the page. Clicking Return brings you back home. It just plain works.
- So hey, notice how that page doesn't render terribly nicely? The toolbar is fine, but the actual page content ain't so hot. This is something that really bugged me. All of the demos focused on menus and forms, but never showed "simple" content in a nice display. I asked about this on the Google Group (by the way, another big tip is to use the jQTouch Google Group) and the suggestion was to add CSS. I'm kinda surprised this doesn't exist already. Here is the CSS I added for my application.
<style> .body { -webkit-border-radius: 8px; -webkit-box-shadow: rgba(0,0,0,.3) 1px 1px 3px; padding: 10px 10px 10px 10px; margin:10px; background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#4c4d4e), to(#404142)); word-wrap:break-word; } .body a { color: white; } .body p { margin-bottom: 10px; } </style>
I then wrapped the paragraphs in my 2 "pages" with the div class="body" tags. The result is a better looking page (imho):
- So, I mentioned earlier that by default, jQTouch expects your pages to be in the same page. I don't think that makes much sense for a dynamic application. Luckily it's easy enough to work around. Any link that is not an anchor link is automatically converted to an AJAX get request. Let's add a link to our menu:
<ul class="rounded"> <li class="arrow"><a href="#about">About</a></li> <li class="arrow"><a href="#unicorn">Unicorn</a></li> <li class="arrow"><a href="cylon.html">Cylon</a></li> </ul>
Next we add a cylon.html file. Remember this is loaded via Ajax. Therefore, we just need our toolbar and body divs.
<div class="toolbar"> <h1>Cylons</h1> <a class="button cancel" href="#">Return</a> </div> <div class="body"> <p> Remember the basic rule. If the person is ugly, they can't be a Cylon in disguise. </p> </div> </div><div id="about">
- Ok, so what if you don't want to use AJAX for the request? For example, I had a logout link and I needed it to reload the entire page. This blog entry by Tim Golen describes all the way links work. If you don't feel like clicking the link, just add rel="external" to your links.
So the fact that AJAX is used to load stuff brought up two interesting problems for me. The first problem was authentication. My application has a login screen and I present the jQTouch UI after you logged in. I did this with a simple location() call in onRequestStart. This breaks when the AJAX request calls a page and you've logged off. I don't have my solution yet - but what I'm going to do is detect the AJAX request (which is super easy) and output something jQTouch can pick up on. Speaking of that...
The second issue I had was trying to do crap when the page loaded. I built my pages with the assumption that I could just use $(document).ready. This kinda worked. Well, no, not really. Most likely it was something I did wrong. jQTouch provides a set of events that you can listen to for your application. The event you want to use is pageAnimationEnd. The example in their docs though assume a one page app and makes use of bind. I had to make use of live instead. Here is an example:
$('#somepage').live('pageAnimationEnd', function(event, info){ if (info.direction == 'in') {
In this example, somepage is the ID of the main div around the page. info.direction will either be in or out. I used "in" as a way of handling the page load and "out" as a way of handling the page ending. This was pretty useful for me as the page in question needed a JavaScript interval. So I started it up when the page loaded and killed it when the page ended.
That's all I have for now. I linked to the Google Group above (but here it is again) - I'll also point out the Wiki: http://code.google.com/p/jqtouch/w/list. It is a bit more fleshed out then I remember it from last time. I've pasted a zip of my simple demo above. It should work fine anywhere. For my testing I use Safari shrunk as small as possible. I've also tested my application on both my Nexus One and an iPod Touch, and in both it looks fantastic. So despide the "roughness" of the framework for me, I definitely think it's pretty darn cool! I hope these tips help, and if anyone wants to add some additional tips, be my guest.
Archived Comments
Great post Ray. I have been using jqtouch for a while and absolutely love it. For the Ajax part I do it a different way. I have a JS function that calls a cfc. That cfc returns content that will fill the innerHTML of a div in the main body. I then use JS to trigger the animate function to switch to that div.
Using jqtouch was what got me into doing things with base64 images.
--Dave
@Ray,
Good stuff - after you tweeted about it this weekend, I was definitely interested to see where you went with it. In fact, after you tweeted about it, I decided to look into some CSS animations (which jQTouch uses -- wow, that was a humbling few hours).
I played with jQTouch a while back and found it to be very cool as long as it did exactly what you wanted; I think , like you, when you need to change it up a bit, it suddenly becomes a bit wonky. I had a situation where I wanted to bind() to a link that loaded an AJAX page and have the page that it linked to show a "loading..." string prior to the AJAX actually having been loaded.
After like 4 hours, I couldn't get that to work at all. I ended up having to manually trigger the "touch" event... and found out that manually triggering it didn't seem to actually doing anything...
Anyway, I'm feeling very inspired to see you working on this. Thanks!
If Adobe could find some way to integrate JQTouch or something similar into CF10 then it could have the same impact as the introduction of Ajax had for CF8.
I was actually considering whipping up some custom tags to abstract some of this out.
<cf_jqtouch>
<cf_jqtouchhome>
<cf_jqtouchmenu label="About" src="about">
</cf_jqtouchome>
<cf_jqtouchpage id="about">
This is the abouve
</cf_jqtouchpage>
</cf_jqtouch>
custom tags - yes please
It's actually Sencha Touch now
**can't post url. flagged as spam.
Sorry the URL was flagged. So I knew it was 'with' Sencha, but has it been renamed? The jQTouch site doesn't seem to say that (but I may have missed it.)
ya, found it weird that it didn't say anything.
add this to the url.
/blog/2010/06/17/introducing-sencha-touch-html5-framework-for-mobile/
@ray I was looking at your example using one of the items to load an html file instead of linking to an existing div (point 6 on the post). This technique intrigued me as I was unaware that it was even possible. In playing with it I found out a couple interesting things.
First, there is a minor error in your example code. The primary div in the cylon.html has the same id as an already exiting div. Since these need to be unique this causes an issue if you click on the cylon menu item again.
Along with this it would appear that the href of the cylon menu item is rewritten to the id of the returning div after first click. The fact that the href is rewritten this would mean that it will not make subsequent calls to the server for new content. I have not yet found a way around this yet as it appears to happen due to jQtouch functionality.
Thanks,
--Dave
I'll try to fix that typo asap. To your second point, jQTouch will, by default, only load a remote item once. However, you can change that as a config option. I don't have the docs in front of me right now, but it's a simple boolean argument.
I found the config option, it is: cacheGetRequests. However, what ends up happening is that the call is made and the new return is added to the dom. So, if you are loading a lot of content the local content could get quite large since the previous load is left behind and not removed.
The content also gets the same div id unless it is dynamically changed. I am not sure what this might do to the app after continued, same session, use. It would be cool if the previously loaded content could be destroyed.
--Dave
Seems like that would be a memory issue. Have you raised that on the mailing list?
Nope.. I will though.
For those wondering about the relationship / difference between jQTouch and Sencha Touch, read David Kaneda's blog post:
http://9-bits.com/post/7237...
He created jQTouch first, which is of course powered by jQuery and MIT licensed. Similar to jQuery the main idea is to provide progressive enhancement to mobile browsers as we often do on normal websites for desktop browsers.
Sencha (previously Ext) hired him to build Sencha Touch. Similar to Ext JS the main idea is to build JavaScript-based applications for mobile devices.
So jQTouch hasn't been "replaced" by Sencha Touch by any means, they're separate libraries with slightly different use cases.
Justin, thank you very much for these details!
@Justin: thx for that. Nobody ever really explained it. Seemed like Ext guys were taking it over.
Raymond, thanks for this. I'm building a web app that's using Ajax to call in a lot of outside HTML pages. I wonder if you can expand at all on "doing crap" after you call cylon.HTML - I need to run a JavaScript on the called page, but am having no luck...and I'm not experienced enough with Ajax or js to know what I dont know. Would really appreciate any insights.
Raymond, thanks for this. I'm building a web app that's using Ajax to call in a lot of outside HTML pages. I wonder if you can expand at all on "doing crap" after you call cylon.HTML - I need to run a JavaScript on the called page, but am having no luck...and I'm not experienced enough with Ajax or js to know what I dont know. Would really appreciate any insights.
I had to reread the post - it has been a while. :) So point 9 (may be hard to see - forgot to add a paragraph tag) talks about how you listen for the 'in' event for page navigation. Did you try that?
I did Raymond, and I think that's where the answer lies - though it's murky for me. Would I discuss the #somepage that launches the ajax page, or would I discuss the remote page? That is:
$('#somepage').live('pageAnimationEnd', function(<script type="text/javascript" src="http://remotesite.com/js/so..."></script>){
if (info.direction == 'in');
});
Where inside the #somepage div I have a <ul> menu, of which one of the <li> points to mypage.html -
Or:
$('folder/another/mypage.html').live('pageAnimationEnd', function(<script type="text/javascript" src="http://remotesite.com/js/so..."></script>){
if (info.direction == 'in');
});
None of these actually work, by the way, and my syntax is crap, I'm sure, but hopefully my question is clear enough for some guidance?
By the way, to muddy the waters further - when I do just put that <script>javascript</script> inside the </head> section of the main page, the javascript fires just fine on the remote pages using iOS devices. On Android, though, they break - so I'm looking for a cross-browser workaround.
I understand I'm kind of taking a tangent from your post/blog purpose, so really - any thoughtful assistance you can provide is so appreciated.
Both are totally wrong. :) You don't use the <script> tag like that. Instead, just use code.
$("#somepage").live("pageAnimationEnd"), function(
alert('hi ray');
);
Oh! Thank you so much for the paragraph CSS. I was looking for exactly the same things. Was really going crazy with no margins and poor formatting of the paragraph tag.
You are an angel :)