Raymond Camden's Blog Rss

PhoneGap RSS Reader

36

Posted in Mobile, jQuery, JavaScript, HTML5 | Posted on 10-11-2011 | 6,356 views

Earlier today I noticed an entry on the PhoneGap forums from a user asking about an RSS reader. I thought I'd whip one up using PhoneGap and jQuery Mobile and see what it took. Here's what I came up, and as always, feel free to rip apart the code and write it better. I'll warn folks that I only wrote the code to support RSS2, not ATOM, but in theory, it should be possible without too much additional work. (You would simply look at the metadata, note it, and change how you get data.)

I began by creating a new PhoneGap project. I strongly recommend reading my entry from earlier today about the nice Eclipse PhoneGap plugin to make it quicker to setup your projects. I began by adding code to handle getting the RSS code. First, I setup two simple variables people could tweak:

view plain print about
1//EDIT THESE LINES
2//Title of the blog
3var TITLE = "ColdFusion Jedi";
4//RSS url
5var RSS = "http://feedproxy.google.com/RaymondCamdensColdfusionBlog";

My code listens for my initial page to load. When this happens, I want to get the XML from the feed and work with it. Here's that snippet.

view plain print about
1$.get(RSS, {}, function(res, code) {
2 var xml = $(res);
3 var items = xml.find("item");
4 $.each(items, function(i, v) {
5 entry = {
6 title:$(v).find("title").text(),
7 link:$(v).find("link").text(),
8 description:$.trim($(v).find("description").text())
9 };
10 entries.push(entry);
11 });

As a reminder, AJAX code in PhoneGap applications are not restricted by normal remote domain rules. I can easily open up the remote XML and parse it. Where did entries come from? Well this is where things get slightly complex. I'm going to store the parsed RSS in a global array called entries (hence the push to add things to the end). This will allow me to dynamically display the pages. But first, let's render out the list. My home page already has a blank list just for this purpose:

view plain print about
1<div data-role="content">    
2    <ul id="linksList" data-role="listview" data-inset="true"></ul>
3</div>

So my handler can simply append to it....

view plain print about
1//now draw the list
2var s = '';
3$.each(entries, function(i, v) {
4 s += '<li><a href="#contentPage" class="contentLink" data-entryid="'+i+'">' + v.title + '</a></li>';
5});
6$("#linksList").append(s);
7$("#linksList").listview("refresh");

Note two things here. First, every single link will be going to a new jQuery Mobile page, contentPage. But I store the ID of the item in a data attribute. Also note the use listview("refresh"). This tells jQuery Mobile to add unicorn magic prettiness to HTML I've added in the list. Serious - Unicorn Magic Prettiness is the official term.

Ok, so what's going on with the content page? I created it as a blank page, like so:

view plain print about
1<div data-role="page" id="contentPage">
2
3    <div data-role="header">
4        <a href="#mainPage" data-rel="back">Home</a>
5        <h1></h1>
6    </div>
7
8    <div data-role="content" id="entryText">
9    </div>
10        
11</div>

And I've got a JavaScript click handler that notices the clicks and populates the data. First, the event listener for the link:

view plain print about
1//listen for detail links
2$(".contentLink").live("click", function() {
3 selectedEntry = $(this).data("entryid");
4});

And then the page displayer...

view plain print about
1//Listen for the content page to load
2$("#contentPage").live("pageshow", function(prepage) {
3 //Set the title
4 $("h1", this).text(entries[selectedEntry].title);
5 var contentHTML = "";
6 contentHTML += entries[selectedEntry].description;
7 contentHTML += '<p/><a href="'+entries[selectedEntry].link + '">Read Entry on Site</a>';
8 $("#entryText",this).html(contentHTML);
9});

Pretty simple, right? (I should point out jQuery Mobile does support completely virtualized pages too. ) Here's a quick screen shot...

And another one...

And finally, the complete code. If anyone wants the actual APK for this, just ask. The home page first:

view plain print about
1<!DOCTYPE html>
2<html>
3    <head>
4    <meta name="viewport" content="width=device-width, initial-scale=1">    
5    <title></title>
6    <link rel="stylesheet" href="http://code.jquery.com/mobile/latest/jquery.mobile.min.css" />
7    <script src="http://code.jquery.com/jquery-1.6.2.min.js"></script>
8    <script src="main.js"></script>
9    <script src="http://code.jquery.com/mobile/latest/jquery.mobile.min.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        <ul id="linksList" data-role="listview" data-inset="true"></ul>
21    </div>
22
23    <div data-role="footer">
24        <h4>SimpleBlog by Raymond Camden</h4>
25    </div>
26
27    
28</div>
29
30<div data-role="page" id="contentPage">
31
32    <div data-role="header">
33        <a href="#mainPage" data-rel="back">Home</a>
34        <h1></h1>
35    </div>
36
37    <div data-role="content" id="entryText">
38    </div>
39        
40</div>
41
42</body>
43</html>

And the main.js file:

view plain print about
1$(document).ready(function() {
2
3 //EDIT THESE LINES
4 //Title of the blog
5 var TITLE = "ColdFusion Jedi";
6 //RSS url
7 var RSS = "http://feedproxy.google.com/RaymondCamdensColdfusionBlog";
8 //Stores entries
9 var entries = [];
10 var selectedEntry = "";
11
12 //listen for detail links
13 $(".contentLink").live("click", function() {
14 selectedEntry = $(this).data("entryid");
15 });
16
17 //Listen for main page
18 $("#mainPage").live("pageinit", function() {
19
20 //Set the title
21 $("h1", this).text(TITLE);
22
23 $.get(RSS, {}, function(res, code) {
24 var xml = $(res);
25 var items = xml.find("item");
26 $.each(items, function(i, v) {
27 entry = {
28 title:$(v).find("title").text(),
29 link:$(v).find("link").text(),
30 description:$.trim($(v).find("description").text())
31 };
32 entries.push(entry);
33 });
34
35 //now draw the list
36 var s = '';
37 $.each(entries, function(i, v) {
38 s += '<li><a href="#contentPage" class="contentLink" data-entryid="'+i+'">' + v.title + '</a></li>';
39 });
40 $("#linksList").append(s);
41 $("#linksList").listview("refresh");
42 });
43
44 });
45
46 //Listen for the content page to load
47 $("#contentPage").live("pageshow", function(prepage) {
48 //Set the title
49 $("h1", this).text(entries[selectedEntry].title);
50 var contentHTML = "";
51 contentHTML += entries[selectedEntry].description;
52 contentHTML += '<p/><a href="'+entries[selectedEntry].link + '">Read Entry on Site</a>';
53 $("#entryText",this).html(contentHTML);
54 });
55
56});

Comments

[Add Comment] [Subscribe to Comments]

Well, thank God for "Unicorn Magic Prettiness", $("#linksList").listview("refresh"); made me look more closely at the docs to see if this was also available for form elements, which of course it was. So happily I can get rid of the hacky way I was accomplishing this before (initing multiple plugins on all the dynamic elements I was adding).

Funny how when you read documentation you can miss things you aren't looking for...
Yep - I'm reading stuff as I write my book and discovering all kinds of little things I missed.
I should point out that the latest jQuery build is 1.6.4.
And? Sorry - am I missing something?
Nice work, as usual, Ray. Have you given any thought to add search and paging?
Henry
Search wouldn't be possible unless you knew the search URL for the remote blog, and even then, the remote blog would need to provide an API to search so that you can get results in JSON (or some other format). Paging also wouldn't make sense because most RSS feeds don't allow for it - they just return the last N entries.
Thank you for this great script. I am trying to enhance it a bit, there is a delay when i like to the page with this code, so you get the header, and a blank page until the LI rss items load....Can you help me add the jquery mobile loading screen while the items are populated.

For some reason adding these did not work for me
// show
$.mobile.pageLoading();

// hide
$.mobile.pageLoading(true);


Thank you
Well, I'd probably do this.

1) In my home page, the first div with data-role=content, I'd add a new inner div above the empty ul. Something like: <div id="preloading">Please stand by...</div>

2) When I get my crap back and before I add the items, then just do $("#preloading").html("")
Thanks for the tip Raymond!
I added the jquery spinning wheel/loading pop up for the stories. Looks pretty nice now. Also changed the external story link to open with browser with - target="_webapp"
Here is my updated script
<script>

//show loader

var showLoader = function () {
$('.ui-loader').css('display', 'block');

}

//hide loader
var hideLoader = function () {
$('.ui-loader').css('display', 'none');
}
         

$(document).ready(function() {
   
// alert('test');
//EDIT THESE LINES
//Title of the blog
var TITLE = "News";
//RSS url
var RSS = "news.xml";
//Stores entries
var entries = [];
var selectedEntry = "";

//listen for detail links
$(".contentLink").live("click", function() {
selectedEntry = $(this).data("entryid");
});

//Listen for main page
$("#mainPage").live("pageinit", function() {
showLoader();     //Set the title
$("h1", this).text(TITLE);


$.get(RSS, {}, function(res, code) {
var xml = $(res);
var items = xml.find("item");
$.each(items, function(i, v) {
entry = {
title:$(v).find("title").text(),
link:$(v).find("link").text(),
description:$.trim($(v).find("description").text())
};
entries.push(entry);

                        hideLoader();
});


//now draw the list
                                    
var s = '';
$.each(entries, function(i, v) {
s += '<li><a href="#contentPage" class="contentLink" data-entryid="'+i+'">' + v.title + '</a></li>';
});
$("#linksList").append(s);

$("#linksList").listview("refresh");
                                          
});
});

    //Listen for the content page to load
$("#contentPage").live("pageshow", function(prepage) {
//Set the title

$("h1", this).text(entries[selectedEntry].title);
var contentHTML = "";
contentHTML += entries[selectedEntry].description;
contentHTML += '<p/><a target="_webapp" href="'+entries[selectedEntry].link + '">Read Entry on Site</a>';
$("#entryText",this).html(contentHTML);

});

});
                        
                        
                     
</script>
Ned some help...so i am trying to open up a news feed page from the main page, to get the jquery mobile transition effect, i am calling it with
$.mobile.changePage( 'news.html', { transition: 'slide'} );"

however when the page slides, i got the blank page, that is not populated wit hthe news stories from the javascript.....any ideas? i would think the document.ready event should make sure its populated after page is loaded? thank you
Well I'd need to see more. When you load a page like that, if it hasn't been injected in the DOM yet then it will be blank. (Well not blank, but whatever is in news.html.) You can't add stuff to that page until _after_ it has been loaded.
Is this code specifically for android. I am trying to apply the code in Xcode with phonegap with no success. Is there any major changes i would have to make.

Thanks
Nope, it should work. I've never done the Phonegap process on iOS, but if you start your project according to their docs, then use my code, it should work. If not, you need to provide me a bit more detail on what is going wrong.
I am having no luck with it. The main.js does not seem to be responding. I have set an alert within the main.js and it won't alert. here is the following html code and main.js-

<!DOCTYPE html>
<html>
   <head>
<meta name="viewport" content="width=device-width, initial-scale=1">   
<title></title>
<link rel="stylesheet" type="text/css" href="style.css">
<link rel="stylesheet" type="text/css" href="jq/jquery.mobile-1.0b1.min.css">
<script type="text/javascript" charset="utf-8" src="phonegap.0.9.6.min.js"></script>
<script type="text/javascript" charset="utf-8" src="jq/jqueryCombined.js"></script>
<script type="text/javascript" charset="utf-8" src="main.js"></script>
</head>
<body>

<div data-role="page" id="mainPage">

<div data-role="header">
<h1></h1>
</div>

<div data-role="content">   
<ul id="linksList" data-role="listview" data-inset="true"></ul>
</div>
</div>

<div data-role="page" id="contentPage">

<div data-role="header">
<a href="#mainPage" data-rel="back">Home</a>
<h1></h1>
</div>

<div data-role="content" id="entryText">
</div>

</div>

</body>
</html>

main.js



var showLoader = function () {
$('.ui-loader').css('display', 'block');

}
alert("test");
//hide loader
var hideLoader = function () {
$('.ui-loader').css('display', 'none');
}

alert("test");
$(document).ready(function() {
alert("test");
//EDIT THESE LINES
//Title of the blog
var TITLE = "News";
//RSS url
var RSS = "news.xml";
//Stores entries
var entries = [];
var selectedEntry = "";

//listen for detail links
$(".contentLink").live("click", function() {
selectedEntry = $(this).data("entryid");
});

//Listen for main page
$("#mainPage").live("pageinit", function() {
showLoader(); //Set the title
$("h1", this).text(TITLE);


$.get(RSS, {}, function(res, code) {
var xml = $(res);
var items = xml.find("item");
$.each(items, function(i, v) {
entry = {
title:$(v).find("title").text(),
link:$(v).find("link").text(),
description:$.trim($(v).find("description").text())
};
entries.push(entry);

hideLoader();
});


//now draw the list

var s = '';
$.each(entries, function(i, v) {
s += '<li><a href="#contentPage" class="contentLink" data-entryid="'+i+'">' + v.title + '</a></li>';
});
$("#linksList").append(s);

$("#linksList").listview("refresh");

});
});

//Listen for the content page to load
$("#contentPage").live("pageshow", function(prepage) {
//Set the title

$("h1", this).text(entries[selectedEntry].title);
var contentHTML = "";
contentHTML += entries[selectedEntry].description;
contentHTML += '<p/><a target="_webapp" href="'+entries[selectedEntry].link + '">Read Entry on Site</a>';
$("#entryText",this).html(contentHTML);

});

});
Ugh, in the future, please, _pretty please_, use Pastebin for code snippets.

Not sure what to suggest to you. That alert should fire. I'd ensure it is in the same folder as the index.html file at minimum.
Hi Raymond,

This is amazing! It works perfect on the iPhone too :)

I was wondering, would this support RSS images too?
The code I have now doesn't look for it. But you could modify it quickly enough.
Thanks for the quick response.

My JS knowledge is pretty poor, but I'm learning :)
This is a great way to have an RSS reader w/o he use of PHP thanks for writing the article!

I am using this code to link to a Youtube channel's rss feed. It works well but I would like to have the user click on the headline and get sent directly to the link instead being sent to the #contentpage. Is that possible? Thanks for any info


Mike
Just change this:

s += '<li><a href="#contentPage" class="contentLink" data-entryid="'+i+'">' + v.title + '</a></li>';

Get rid of the class, the data-entryid, and just add url="X" where X is v.... one sec. link. Yeah, should be v.link.
Hey Raymond,

I followed every direction but the last one, I was a little confusing; what did you mean by "just add url="X" where X is v.... one sec. link. Yeah, should be v.link."

This is what I got so far, I think its really close I just need one more thing to fix it.. check it out if you have the time.

http://jsfiddle.net/lamike/Tsk7G/

Thanks a lot,

Mike
So - you got the link out of the XML, but you didn't use it when rendering the entry data.

var s = '';
$.each(entries, function(i, v) {
s += '<li><a href="#contentPage"'+i+'">' + v.title + '</a></li>';
});

Change the link:

s += '<li><a href="'+v.link+'">'+v.title+'</a></li>';

Watch out for typos there.
I'm trying to put some new tags but my app don't show this!

I try with new tags like "<test>" but with [...] find.(test).text() don't show anything!!

The XML file is generated by a PHP and is validated by W3. Anybody can help me? Thanks! :)
You would need to share a bit more code. If you do so, please use pastebin.
No way, I solved correctly this problem :)

But there is a new problem...

I need to know how I can update or remove saved data in device. If the app have been show XML data, if you close the app and update the XML file with new information in the server and open again the app you can't see the new info... because the data was saved in device like temporary files.

If you go to the app list and clean the saved data you can see the new XML info.

Is there any solution for this?

Sorry for my bad english... :P
What you are seeing is simply the app being resumed. You can listen for resume events in PhoneGap and simply refetch the data. Or, you can also provide a Reload type button in the app so users can refresh it themselves. Or both. ;)
Raymond,

I got the rss feed working about a week on an IPhone app with phonegap and sent it out for testing this week. But today all the apps rss feeds are failing and I dont know why. They were all working fine up to yesterday. The simulator is failing to run the rss feed also.

Just wondering if you would have an idea why this would be happening. Has there been a change in the code of the scripts src or something???

Any advice would be appreciated
Simply add an error handler to the code. Mine example does not have that, and it is a mistake. I can help you with this, but not till Monday - going out of town. If you can't wait, go to jQuery's site and look at how they handle errors for Ajax calls.

And thank you for bringing this up. I was remis in not supporting that immediately. I apologize!
Ok, got a simple example working - will post an update tomorrow.
Hi, raymond, thanks for the tutorial..

I have followed the tutorial well, but I have several question.
1. Everytime I choose one of the rss feed, and then I press the back button, the application load some more feeds.
Example:
The first time application start,
the lists is just:
-A
-B,
but when I choose one of them, and press back to the list, it will be,
-A
-B
-A
-B

How to solve this problem?
2. How can I get image from the rss file in javascript?

Thanks.. :)
I fixed that too (see earlier comment about error handling). The quick fix for you is to simply add entries=[] into the get() callback. Or just wait till later today - I'll be blogging it.
Hey guys - please see my follow up here:

http://www.raymondcamden.com/index.cfm/2011/12/19/...
Oh man you did a very fantastic job. I am very new in Android development and your article helped me out.

Thank you!
can i get the source code for above app pls???
It is in the blog entry.

[Add Comment] [Subscribe to Comments]