Posted in Mobile, jQuery, JavaScript, HTML5 | Posted on 12-19-2011 | 4,184 views
Two months ago I wrote a blog entry on how to build a simple PhoneGapp application. This application just read in an RSS feed and used jQuery Mobile to display the results. I think this was helpful, but a few readers pointed out some issues with the code that caused me to come back to it this weekend and work on some updates. The issues were...
Error Handling
I admit it. I do not do a good job of error handling in most of my AJAX based applications. While this may be acceptable in some applications, in a mobile application where the entire functionality depends on things working right, there is no excuse for lacking proper error handling. My application really just did one main thing - fetch an RSS feed. Everything after that was display. But as with server side apps, any network call is a point of failure. My first change was to add in a simple error handler. In jQuery, this is rather simple. Since the application can't do much of anything without an RSS feed (or can it - see my PS below), for now we just display a simple error message.
2 error:function(x,e,errorThrown) {
3 console.log(x.getStatusCode());
4 $("#status").prepend("Error!");
5 }
6});
That's it. jQuery makes it simple to add global handlers and since we only have one network operation anyway, I can use this just fine. As I said, we don't really need to provide a lot of detail in this case, but we can at least let the user know something went wrong. To be fair, in a real application I'd probably add a bit more text. I'd let the user know the data couldn't be loaded and to please try again.
Page load issues
This one was a bonehead mistake. If you look at the code in the original blog entry, I do my network call and render results using the pageshow event. This means that every time the page is shown, it will fire, including times when the user hits back from an entry view. As I said - bonehead. Luckily it's simple enough to change to pageinit. Another change I made was to not make use of jQuery's document.ready logic. Instead, I simply load everything up at once. Here is my updated JavaScript file in it's entirety.
2 error:function(x,e,errorThrown) {
3 console.log(x.getStatusCode());
4 $("#status").prepend("Error!");
5 }
6});
7
8//EDIT THESE LINES
9//Title of the blog
10var TITLE = "ColdFusion Jedi";
11//RSS url
12var RSS = "http://feedproxy.google.com/RaymondCamdensColdfusionBlog";
13//Stores entries
14var entries = [];
15var selectedEntry = "";
16
17//listen for detail links
18$(".contentLink").live("click", function() {
19 selectedEntry = $(this).data("entryid");
20});
21
22//Listen for main page
23$("#mainPage").live("pageinit", function() {
24 //Set the title
25 $("h1", this).text(TITLE);
26
27 $.get(RSS, {}, function(res, code) {
28 entries = [];
29 var xml = $(res);
30 var items = xml.find("item");
31 $.each(items, function(i, v) {
32 entry = {
33 title:$(v).find("title").text(),
34 link:$(v).find("link").text(),
35 description:$.trim($(v).find("description").text())
36 };
37 entries.push(entry);
38 });
39
40 //now draw the list
41 var s = '';
42 $.each(entries, function(i, v) {
43 s += '<li><a href="#contentPage" class="contentLink" data-entryid="'+i+'">' + v.title + '</a></li>';
44 });
45 $("#linksList").html(s);
46 $("#linksList").listview("refresh");
47 });
48
49});
50
51//Listen for the content page to load
52$("#contentPage").live("pageshow", function(prepage) {
53 //Set the title
54 $("h1", this).text(entries[selectedEntry].title);
55 var contentHTML = "";
56 contentHTML += entries[selectedEntry].description;
57 contentHTML += '<p/><a href="'+entries[selectedEntry].link + '">Read Entry on Site</a>';
58 $("#entryText",this).html(contentHTML);
59});
And here is the front end HTML. The only change here was the addition of the status div used by error handling.
2<html>
3 <head>
4 <meta name="viewport" content="width=device-width, initial-scale=1">
5 <title></title>
6 <link rel="stylesheet" href="js/jquery.mobile-1.0.min.css" />
7 <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
8 <script src="js/jquery.mobile-1.0.min.js"></script>
9 <script src="js/main.js"></script>
10</head>
11<body>
12
13<div data-role="page" id="mainPage">
14
15 <div data-role="header">
16 <h1></h1>
17 </div>
18
19 <div data-role="content">
20 <div id="status"></div>
21 <ul id="linksList" data-role="listview" data-inset="true"></ul>
22 </div>
23
24 <div data-role="footer">
25 <h4>SimpleBlog by Raymond Camden</h4>
26 </div>
27
28
29</div>
30
31<div data-role="page" id="contentPage">
32
33 <div data-role="header">
34 <a href="#mainPage" data-rel="back">Home</a>
35 <h1></h1>
36 </div>
37
38 <div data-role="content" id="entryText">
39 </div>
40
41</div>
42
43</body>
44</html>
There you go! I've wrapped up the entire Eclipse project into a zip. It also includes a debug APK you can install to your device if you want to try it out.
p.s. Technically, we could try to handle network issues better. I'm not just talking about the RSS feed being down, but what if the user is offline? I've decided to follow this up with a third version that will try storing the RSS feed in local storage. If the user is offline, we can at least resort to the older data.


You may remember me, you helped me out when trying to change the main.js file to link directly to the article instead of going to the #contentpage and clicking a link... It worked great on my browser, so I just decided to put it into xcode, but I ran into a problem; there was no content on the page :(
This is what the simulator looked like:
http://bit.ly/rMkwxL
So I thought it was a problem w/ linking to the main.js file, so i just included the javascript in the header, but still nothing. This is what the Xcode console looks like as of right now:
http://bit.ly/uY0Xmb
Do you know what I can do to make this work on Xcode?
<iframe src="http://pastebin.com/embed_iframe.php?i=NyYj0sax&qu...; style="border:none;width:100%"></iframe>
http://pastebin.com/raw.php?i=NyYj0sax
changed the console.log to alert, but still no luck... do I have to add anything to the external host p.list? I already have *pipes.yahoo.com on there
$("#mainPage").live("pageinit", function() {
add alert('Ray');
and see if that shows up. Want to ensure the pageinit handler is running.
No alert.... did I put it in the right place?
http://youtu.be/z9QBUea_T2E
Right?
Thanks for this awesome tutorial. I mean, I could have easily ripped the code but the step by step really helped me grasp the concept. I've since added a few things but this was exactly what I was looking for.
Thanks again!
I did run into a small snag trying to get this to run properly with phonegap after a minor modification.
I added an entry to the main.js right below the others:
content:$.trim($(v).find('content\\:encoded').text())
I did this simply because my rss feed doesn't spit out much in the description.
I also changed this:
contentHTML += entries[selectedEntry].description;
to
contentHTML += entries[selectedEntry].content;
Everything works great in the browser (http://parkrumors.com/mobile)
The only thing different between the browser and apk is the inclusion of phonegap js and the addition of some other pages. When I revert the contentHTML back to description, it works fine in the emulator.
I think I made the issue clear? Your thoughts?
Thanks for your help!
content:$.trim($(v).find('content\\:encoded').text())
to
content:$.trim($(v).find('encoded').text())
I'm researching now as to _why_ this match works.
https://plus.google.com/115106614688778962135/post...
$(v).find("content:encoded, encoded").text();
In the bug and it works well. Here is more details on the bug: http://bugs.jquery.com/ticket/10377
You've gone above and beyond! You sir have made my day! (Side note, today is also my Birthday!) lol
Thanks so much!
I'm rather new to Phonegap development. But looking at your example I hacked together something that stores the contents in an sql database and then retrieves it from there if the user isn't online (which is which I think you described you would do in installment 3). It uses the title of the post as a unique identifier -- that will likely work 95% of the time. It will theoretically also store old entries that have disappeared from the stream.
I wonder though what you think baout the following:
1. The fact that I escape the entire text string before putting it in the DB.
2. The fact that it seems impossible to use a db that is not called 'test' (without getting an error).
3. The combination of phonegap and jquery listeners (looks dirty).
4. If you have any opinions about how to organize image caching (currently not working).
Code: http://pastebin.com/9gXXmDV6
1. Take the description text of each entry and replace each instance of <img src="..."> with <canvas id="..."> before saving it in the database
2. Create a new table in the DB called fx IMAGES. For each of the above <img>s, download the image file and save them in the IMAGES table using base64 encoding. A second column in that table should specify the id that was used in the canvas-element that we inserted under 1. A third column in the table should specify which RSS-entry the image was on.
---
3. When retrieving an RSS entry, also download all the entries in the IMAGES table that are related to it.
4. After inserting the RSS-entries into the HTML, for each of the entries from the IMAGES table we retrieved in 3, use drawImageOnCanvas to draw the base64-encoded image data on the corresponding canvas element.
This is basically what I would go for. The reason I'm hesitating is that it seems a fairly simple operation, and I haven't found any references to doing it like this anywhere on the web. Does it seem right to you though?
1) Primary key: You can probably find a decent JS library to simulate UUIDs. You can also use autonumbering in SQLite. Since this is a single user db, you could single thread the insert, get max(id), and have your new id. I'd probably do that myself.
2) Escaping text... don't do that. For your insert, use a bound parameter. (If that doesn't make sense, just ask.)
3) Sorry - what? :) (In terms of phonegap/jquery event listeners)
4) Image caching: Your solution is interesting, but don't forget you can use data urls too. That may be easier than a canvas implementation.
I still can't get images to show on mine. Using FeedBurner I can't figure out what tag[s] are being used to show images.
Have you implemented images before?
Thanks,
The feed URL is: http://feeds.feedburner.com/SerenserenSeren
I've also tried using 'content' rather than 'desrciption' by including...
content$(v).find("content:encoded, encoded").text(),
...then for displayinh on contentPage:
contentHTML += '<p> '+entries[selectedEntry].content +'</p>';
I thought by showing content it would show images.
Thanks,
If its any use...I'm implementing it on Xcode.
contentHTML += '<p> '+entries[selectedEntry].content +'</p>';
alert(contentHTML);
Unfortunately, nothing is responding in the app. It stopped working since I changed from using 'description' to using 'content'.
I get the header and footer but nothing in between, therefore can't see the contentPage for the alert to work.
Replaced:
content$(v).find("content:encoded, encoded").text()
With:
content:$.trim($(v).find("content:encoded, encoded").text()
It now displays the articles with proper paragraphs etc. Image placeholders/boxes are shown but the actual images are not showing. I have ensured that I have declared the allowed ExternalHosts in Xcode (i.e. the website/image sites reference. But they will not display.
So what I'd do now is simplify. Start a NEW project, virgin code. Use JavaScript to set a div to HTML like so:
$("#somediv").html("<h1>Hello</h1><img src='....'>");
In other words, use a very simple test best that uses HTML with an external image URL and see if that works out alright.
I have been build an app to read differents XML (in different div pages in the same HTML file), with a fixed navbar when I'm viewing the first XML list and I would like to go to the other list clicking in a link of the navbar, have this effect:
- Show the new page
- Show the old page
- Show and stay in the new page.
This effect shows in 1 second...
why it's show this?
I would like to go to the new page without this effect. I have tried with all options of data-transition but nothing...
Maybe the problem is on $("#list").listview("refresh"); ???
Sorry for my bad english.
Gracias (Thanks) :)
I fixed my previous problem with displaying images. It was in fact a whitelisting URL (external hosts) issue. All fixed now and looking great.
I've also modified the code with Part 3 caching etc, works great.
I was wondering, is it possible to render thumbnails in my list? Either as media:thumbnail or by somehow extracting the first image from each post?
Thanks,
http://jquerymobile.com/demos/1.0.1/docs/lists/lis...
But as to the URL, you would have a few options. First off, I do not quite remember if the RSS spec supports a icon per item. If you check the content of the RSS item, you can use a regular expression to find an image, and if so, include it.
I've uploaded my XML feed source to pastebin: http://codeupload.com/5147 .
How do you recommend implementing a regular epression?
Where would I put the expression in the main.js? I found a good example that picks up the first image of a html source... http://zytzagoo.net/blog/2008/01/23/extracting-ima... ... but its in PHP.
var imgpat = /<img.*?>/g;
var matches = str.match(imgpat);
console.dir(matches);
I inserted this into the main.js just before '$.each(entries, function(i, v) {' . But the app is not showing any content now. Can you tell me where I need to insert it and how to call in the thumnail list item?
Thanks,
Thanks. Been trying everything for 3 days now but all examples are in PHP, which Phonegap/Xcode won't allow. Not giving up though :).
The JS I inserted into my HTML <body>:
http://codeupload.com/5160
What I've done in your my main.js:
http://codeupload.com/5161
What the app currently looks like...
List: http://d.pr/U2tn
Entry: http://d.pr/KK9a
Thanks :)
entry = {
link:$(v).find("link").text(),
pubDate:$(v).find("pubDate").text(),
creator:$(v).find("creator").text(),
title:$(v).find("title").text(),
link:$(v).find("link").text(),
llun:$.trim($(v).find("media:content").text()),
commentRss:$.trim($(v).find("wfw:commentRss").text()),
content:$.trim($(v).find("content:encoded, encoded").text())
};
This is the object we add to the array, and it contains all the info we care about for the entry. Since our regex logic is a bit complex, I'd do my mod AFTER the above.
I'd add the code to perform the regex, as I demonstrated. If there are matches, you take that first one and add it to the entry object, so imagine I've done that:
if(matches.length) {
entry.image = matches[0];
}
You may want to supply a blank value too:
} else {
entry.image = "";
}
or use some default image like you've done in your screen shot.
Not bringing anything up in the content area now.
Off topic..but do you have a PayPal donate button on this blog? Or a PayPal email address?
Thanks,
As for donations, I've got an Amazon Wish List link on the right. If you visit it, be sure to let me know as Amazon does a bad job of telling me who got me stuff. Even more, if you give me a UPS tracking #, you can watch my productivity dive in real time as I obsessively reload every 5 minutes.
I've now changed line 51 and its still not bringing back any content at all. :(
http://d.pr/z9mC
I've zipped the HTML, CSS, JS and images here: http://d.pr/R9r4
Raw XML feed is at: http://codeupload.com/5165
Or here: feed://feeds.feedburner.com/SerenserenSeren
Really appreciate you doing this.
this codes returns not only title, but also <media:title>
<title>My Title</title>
<media:title type="html">spoon-01</media:title>
So the title in the main page shows "My Titlespoon-01"
var imgpat = /<img.*?>/g;
var matches = entry.content.match(imgpat);
console.dir(matches);
if(matches.length) {
entry.image = matches[0];
}
} else {
entry.image ="";
}
See the else block? You have 2 } before it.
I've changed it now and am calling '+ entry.image +' in my list.
Its bringing back an image, but the same one (from my last article entry. Screenshot here: http://d.pr/Ov4r .
Is it something to do with looping to the last entry and only bringing back that image?
Thanks,
Image is called at line 20.
Thanks,
$.each(entries, function(i, v) {
s += '<li style="-webkit-border-radius:0px;"><a href="#contentPage" class="contentLink" data-entryid="'+i+'" style="-webkit-border-radius:0px;">' + entry.image + ' ' + v.title + '<br><span class="published-date-list">' + v.pubDate + '</span><br><span class="creator-list">by ' + v.creator+ '</span></a></li>';
});
Inside the loop, v represents the current entry you are going over. Change entry.image to v.image.
thumb.jpg is a blue image.
Thanks for all your help, I will stop pestering you now :)
Quick question, do this code show RSS images too?
Thanks for your work Ray.
your work is very good, so thanks a lot. However I have a little problem: the code in the brackets (entry.image = "/images/thumb.jpg";) displays the advertising of my rss feed and not a local thumb.
Thanks for your help!
Thanks
Thank you!
A way around this would be to modify the regex to look for images that include a src pointing to your main site. Of course, if you ever put an image in your blog that is from another server, then it wouldn't work.
i got a strange problem with your code...
it shows the rss feed in safari and dreamweaver just fine.
but when i try it on my iphone or in firefox no items are loaded.
do you have any idea what the problem could be?
thx a lot!!!!
I just solved some problems but I don't know how to autoresize my thumb in the listview!
In other words, this is an HTML issue, not a PhoneGap issue.
Last question: How can I implement a context menu in "contentPage" to share my post? Best regards!
How do I format the pubDate? I would like to delete +0000 and set up the timezone.
Thank you!
Any suggestions?
[Add Comment] [Subscribe to Comments]