About a week or so ago I whipped up a simple demo that showed using swipe-based navigation within jQuery Mobile (Using swipe gestures for navigation in jQuery Mobile). The idea was simple - look at how jQuery Mobile could listen for swipe events and then use them to navigate to the next page instead of using the traditional button click. I don't know about you, but it's amazing how quickly the swipes seem to be becoming a standard way to work with an application. A few weeks back I found myself about to reach out to my desktop monitor to do a swipe against the screen. My daughter, all of 9, already learned that swipes work on an iPhone, and when I handed her my Nook, she just knew a swipe would work in a book. So I think there's a good reason to work with this type of user interaction. Today's blog entry is another example of this - an Art Browser.
What follows is the complete code for my application. It's a one page application that handles getting art work from the database and displaying one image at a time. Swipe events can be used to go to the next piece of art, or return to the previous entry. Let's look at the code.
<!DOCTYPE html>
<html>
<head>
<title>Art Browser</title>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.css" />
<script src="http://code.jquery.com/jquery-1.5.min.js"></script>
<script src="http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.js"></script>
<script>
$(document).ready(function() { $('.artbrowserPage').live('swipeleft swiperight',function(event){
if (event.type == "swiperight") {
var prev = $("#previndex",$.mobile.activePage);
var previndex = $(prev).data("index");
if(previndex != '') {
$.mobile.changePage({url:"index.cfm",type:"get",data:"index="+previndex},"slide",true);
}
}
if (event.type == "swipeleft") {
var next = $("#nextindex",$.mobile.activePage);
var nextindex = $(next).data("index");
if(nextindex != '') {
$.mobile.changePage({url:"index.cfm",type:"get",data:"index="+nextindex});
}
}
event.preventDefault();
});
});
</script>
</head>
<body> <cfoutput>
<div data-role="page" data-theme="e" class="artbrowserPage"> <div data-role="header" data-backbtn="false">
<h1>#artdata[url.index].name#</h1>
</div> <div data-role="content">
<p>
<img src="artgallery/#artdata[url.index].image#" title="#artdata[url.index].name#"><br/>
<br/>
Item #url.index# out of #arrayLen(artdata)#<br/>
Swipe left and right to browse.
</p>
</div> <span id="previndex" data-index="#previndex#"></span>
<span id="nextindex" data-index="#nextindex#"></span> </div>
</cfoutput> </body>
</html>
<!--- load up our index of art if we don't have it already --->
<cfset artdata = cacheGet("artdata")>
<cfif isNull(artdata)>
<cfquery name="getart">
select artname, largeimage
from art
</cfquery>
<!--- quickly filter to art we have a picture for, in a real app this wouldn't be an issue --->
<cfset artdata = []>
<cfloop query="getart">
<cfif fileExists(expandPath("./artgallery/#largeimage#"))>
<cfset arrayAppend(artdata, {name=artname, image=largeimage})>
</cfif>
</cfloop>
<cfset cachePut("artdata", artdata, createTimeSpan(0,0,1,0))>
</cfif>
<cfparam name="url.index" default="1">
<cfif not isNumeric(url.index) or url.index lte 0 or round(url.index) neq url.index or url.index gt arrayLen(artdata)>
<cfset url.index = 1>
</cfif>
<cfset previndex = nextindex = "">
<cfif url.index gte 2>
<cfset previndex = url.index-1>
</cfif>
<cfif url.index lt arrayLen(artdata)>
<cfset nextindex = url.index+1>
</cfif>
Before we dive in - you should definitely read the previous entry to get up to speed with the basics. The first 16 lines or so represent my business logic. Normally this would not be in the page at all. For simplicity sake though I've got it included here. If you don't know ColdFusion (and I expect I'm getting visitors to the site lately who don't), the basic gist is - "Check the cache for an array of art data, and if it doesn't exist, query the database and check the file system for available images to store into my cache." At the end of this block we've got an array of art names and image urls.
The next block is also ColdFusion specific. It simply looks for a URL parameter to indicate which piece of art we are viewing. I do a basic bit of validation (we all validate our URL parameters, right?) and then create variables to represent the previous and next index. Note they will be blank at the beginning or end of the art list.
Ok, now for the JavaScript. I begin with a jQuery selector based on the class of my page. Again, if you've read my previous entry, you will remember that I need my event listener to pick up new 'pages' as they come into the DOM. Since I have the same page being loaded with different data inside, the class based listener is going to pick up on the changes. (I really think that this particular method is something I'm going to change later on. It just doesn't feel... 100% solid. Please keep that in mind. jQuery Mobile is new to me as well)
Inside this event handler is code very similar to my previous example. I'm storing my data a bit differently (as we will see later in the code) so I have to fetch it differently, but the concept is the same. Based on the direction I'm swiping, I need to possibly change pages. Pay particular attention to the swiperight section. For that event, I want to reverse my transition. So I have to specify the second attribute ("slide") as well as the 3rd "false" to signify the reversed version of the normal slide transition. This took me a little bit to figure out. The short version is - this is how I reverse the page transition. The example in the next IF block uses all the defaults.
So - the HTML is rather simple. I output a dynamic record from my cache. I then use two spans to store data values for my index. These will either be a number or a blank string, and my jQuery code will handle them accordingly.
You can play with this here:
It works - and works well I think - but I'd like to change two things:
- The art image is a bit small on the device. Not unsuable of course, but I'd like to make the image bigger.
- Todd Sharp suggested preloading the images. I haven't done image preloaders since the last time I wrote a simple rollover, but I think that would be a good idea. If we expect a user to possibly sit there and stare at the art for a few seconds, we should have enough time to fetch the next image. Since jQuery Mobile loads the next page via Ajax, we could actually fire off an immediate process to start fetching all the images in order. That's it - I've decided - I'm going to do a follow up and try that.
That's all I've got. Any comments on the implementation?
Archived Comments
Yikes lots of room for improvement in that code. But you'll get better in time. Keep practicing Ray. :)
Ouch. Considering how little JS is here - that's pretty damning. Got any thing you can point out in particular?
Cool demo.
One thing that you may want to revise is the #previndex and #nextindex.
You used IDs to identify the data, but remember that each page remains on the page, and IDs are meant to be unique. If you are scoping an ID selector jQuery call, it's a red flag.
You could easily change the spans to use classes instead of IDs, but my suggestion would be to remove the spans all-together. Then in each page div, add data-previndex and data-nextindex attrs. It should simplify the whole process.
Absolutly great Tutorial! Thanx!!!!
A ScreenShot would be cool, so everyone can image the result and might be more motivated to try your tutorial?
@Jim: Yeah - I kinda alluded to that with the pages too. It bugs me. So given that my core JS is loaded once - what would you recommend as a way for it to get spans in a unique way?
I could name the span's in a way that include the current index. <span id="#nextindex-N"> and fetch it that way.
do you plan to make something like fully functioning gallery. Something like that http://tympanus.net/Tutoria...
I went to the site - but I see no special nav. Just a list of pics I can view. Am I missing something?
*ping* Bill Meade. Come on now - you can't put down my code w/o giving me some ideas about what you would change. :)
Thanks for the snippet. Works great.
Thanks for posting this. I am trying to rip what I need from it but I am not great at this stuff. Perhaps you might know this off the top of your head.
I have a multi page jquery mobile setup. So basically all my "pages" (divs) are just on one template.
I have 25 pages and I just want to be able to swipe back and forth between the pages.
Kind of at a loss because I don't know how to tell jquery which link to navigate to / transition to the next page div when you swipe. It's just a static html site, no ColdFusion etc.
Any tips?
If your pages are all within one file, then the URL portion of change url should just be
"#theid"
where theid is the id of the page.
i assume the other parameters like type, data, etc would not be applicable anymore? I seem to be having a difficult time with this.. lol.
i actually found a different solution but its not as robust as I think this one is. It basically just swipes any data-role="page" elements, but i have 20 page divs on one page and only want 10 of them to be swipable.. so i feel a modified solution of what you have would suit me a lot better.
Actually, on reflection, if your pages were all loaded at once (one HTML request) and you want to switch to another page within it, you want to use $.mobile.changePage() instead.
[suggestion] -- When you start to drag, you should see the actual frame moving, and the beggining of the next one, like in the photo app on the iPhone/iPad for example.
Hi Ray,
Really great tutorial! Thanks for sharing.
on the subject of JS, I would suggest a couple of changes:
$('.something').live('swipeleft swiperight', function(e){
if(e.type === 'swipeleft'){
do something;
} else {
do something else;
}
e.preventDefault();
});
would work as well.
btw - $.mobile.changePage() would be used if you're dynamically injecting pages (via server, etc.)
For simple transition between data-type="contents" would be to use the data-transition="slide" on whatever the triggering element.
Good suggestion Peter (comment 1). I disagree with your second statement. I think changePage makes sense any time you want to load a page, where it is being dynamically injected or not. Unless I'm misreading you. (And when I said it makes sense any time, I mean anytime you are changing the page via JS.)
Hi Ray
When I test your swipe code with the jquery mobile codes you used in the demo:
<link rel="stylesheet" href="http://code.jquery.com/mobi..." />
<script src="http://code.jquery.com/jque..."></script>
<script src="http://code.jquery.com/mobi..."></script>
yhe swiping works but the page is not stretchable on an iPad or iPod touch. If i use later versions:
<link rel="stylesheet" href="http://code.jquery.com/mobi..." />
<script src="http://code.jquery.com/jque..."></script>
<script src="http://code.jquery.com/mobi..."></script>
The page is stretchable but the swiping doesn't work. Any idea why?
Regards
Pat coyne
Yep, the call to get the data element is failing. I'm digging into why now.
Ok, the issue was a change to the changePage API. Here is the new code:
<script>
$(document).ready(function() {
$('.artbrowserPage').live('swipeleft swiperight',function(event){
if (event.type == "swiperight") {
var prev = $("#previndex",$.mobile.activePage);
var previndex = $(prev).data("index");
if(previndex != '') {
$.mobile.changePage("index.cfm",{data:"index="+previndex});
}
}
if (event.type == "swipeleft") {
var next = $("#nextindex",$.mobile.activePage);
var nextindex = $(next).data("index");
if(nextindex != '') {
$.mobile.changePage("index.cfm",{data:"index="+nextindex});
}
}
event.preventDefault();
});
});
</script>
Thanks a lot Ray. Works very well.
What if the device does nto support screen touch, wha happens?
What devices don't support touch?