PhoneGap Cordova JSONP RSS Feeds

Datetime:2016-08-22 23:50:10          Topic: JSONP           Share

Introduction

I like creating apps packed with multiple functionality so without re-writing any code I can spin off a new app with a particular theme. The typical features I pack into an app are thngs like searching and displaying YouTube videos, iTunes music videos, RSS Feeds, scanner, dozens of html5 games, AdMob Ads, iOS7 Frosted Panels, and a novel GUI called SwipeClouds ™ all wrapped into a single PhoneGap Cordova mobile app under 10 megabytes. You can see a video of such a "Packed App" I called StationBreak where all these features are turned on at: 

https://www.youtube.com/watch?v=lgn2lXv4szg

Or download a working copy of this app and source code from my website at:

http://www.SerGioApps.com

OR, the compiled app from Google Play at:

https://play.google.com/store/apps/details?id=com.sergioapps.stationbreak

The sample with this article I kept simple and focused on illustrating the use of JSONP RSS Feeds. The most important component in a "Packed App" is a RSS Reader. There are millions of RSS Feeds so by just setting the feeds to a particular Theme such as heath, sports, money, blogs, government grants, etc. , you have created a new mobile app. If I only retrieve feeds in my app on "Dieting" then I have created the "Diet App," or of I only retrieve Government Grants feeds (the U.S. government has over one million such feeds), the I have created the Government Grants Mobile App with the same RSS reader code. For most of these feeds you can use a JSONP helper service such as Google or others. But for certain cusom RSS feeds to deliver aggregate content and TV Ads to mobile apps you can't use services like Google because they cache the responses and you have no control over the caching mechism.

There are plenty of samples of JSONP on CodeProject and the web of using PHP to return JSONP Feeds but I wanted to use a .NET Handler on my Wondows Server. So this article is about using a .NET Handler to retrieve your own, custom, non-standard JSONP RSS Feeds in a PhoneGap Cordova Mobile that support paging and Categories in Categories . Included is a "www" folder of the html5 code and SQL script to create the RSSFeeds database. The sample code also requires you have already installed Microsoft's Northwindd Database.

The sample PhoneGap Cordova mobile app demonstrates how to retrieve a list of hundreds of Categories, and when a user clicked on an item in that list it would, in turn, display a new list of hundreds of items, and when a user clicked on one of those items it would, in turn, display a list of hundreds of items and so on and so on for as many levels deep as you wanted.

For each list of hundreds of items we display 50 items at a time and we provide "Prev" and "Next" buttons so the user can move though hundreds  of items 50 items at a time. And when a user clicks on any item we want to have the option of any one these actions occuring: occuring:

  • Clicking on an item brings up a new list of hundreds of items
  • Clicking on an item plays a single video, movie, or TV commercial
  • Clicking on an item launches a list of videos in a YouTube channel
  • Clicking on an item launches a new web page
  • Any other action you wanted to program to occur

Installing the Sample

Unzip the project file and run the SQL script, rssfeeds.sql, to create the RSSFeeds database and sample data. Also, pLease remember to change the SQL connection strings in the  web.config file to your own SQL Server.

Introducing JSONP or JSON-P

To accomplish this only requires one SQL table and a modified RSS JSONP Feed on your server to side step the same-origin policy. The same-origin policy in browsers requires certain types of data via JavaScript are restricted to where the target resource's domain is identical to the page making the request. This policy protects users from unsafe malicious JavaScript. In PhoneGap Cordova Apps we use Cross-domain Ajax which refers to the idea of making requests across domains in opposition to the same-origin restriction. However, cross-domain Ajax is not inherently unsafe and is used in the most popular and useful apps. One mechanism which can request content cross-domain is the <script> tag. In December 2005, Bob Ippolito formally proposed JSONP (later dubbed JSON-P, or JSON-with-padding) as a way to leverage this property of <script> tags to be able to request data in the JSON format across domains. Read Bob's idea at: http://bob.ippoli.to/archives/2005/12/05/remote-json-jsonp/

JSON-P works by making a <script> element (either in HTML markup or inserted into the DOM via JavaScript), which requests to a remote data service location. The response (the loaded "JavaScript" content) is the name of a function pre-defined on the requesting web page, with the parameter being passed to it being the JSON data being requested. When the script executes, the function is called and passed the JSON data, allowing the requesting page to receive and process the data. I won't go into a discuss here of all the methods used or issues related to security since there are plenty of articles on those tpics here on CodeProject already. This article and sample PhoneGap Cordova Mobile App "www" folder will illustrate one way to implement your own JSONP Modified RSS Feed System on your server for mobile apps using JSONP instead of XML and WITHOUT using Google's jsonp that caches searches which can be a big problem. This doesn't eliminate the use of Google, YouTube, and other services but only adds another tool in your mobile tool belt.

The RSS JSONP Handler

The design concept I used was that instead of having multiple generic handlers or a single handler with lots of switch/if statement I decided to use a single generic handler with Factory design pattern. The Factory returns a class based upon the methodName that is passed which is used to only handle that request. The Factory reads the methodName and its class from the web.config file and instantiates the handler. The generic handler requests the Factory for the handler and performs some pre/post processing. The code of the ProcessRequest method of the rsshandler.ashx file is as follows:

public void ProcessRequest (HttpContext context) {
		BaseHandler handler = null;
		try { handler = HandlerFactory.CreateHandler(context);
			if (handler != null) {
				handler.Execute();
				string output = string.Empty;
				string callbackMethodName = context.Request.Params["jsonp"];
				if (!string.IsNullOrEmpty(callbackMethodName)) {
					string callback = string.Format(CultureInfo.CurrentCulture, 
					"{0}({1}, '{2}');", callbackMethodName, handler.Output, handler.MethodName);
					output += callback;
				}
				else { output = string.Format(CultureInfo.CurrentCulture, 
					"var {0} = {1};", handler.MethodName, handler.Output);
				}
				context.Response.ContentType = "application/x-javascript";
				context.Response.Write(output);			   
			}
			else { context.Response.StatusCode = 404; }
		}
		finally { if (handler != null) { handler.Dispose(); } }
	}

The CreateHandler Method

The Factory gets the request for the generic handler and executes the handler if it exists. Then, depending upon if the callback is specified or not, the result is formatted. Finally it sets the response content type and returns the response. A 404 error is thrown if the handler is not found. The Factory code reads the config file and creates the handler using reflection in the CreateHandler method as follows:

public static class HandlerFactory {
	private static readonly Assembly _currentAssembly = 
	   Assembly.GetExecutingAssembly();
	public static BaseHandler CreateHandler(HttpContext context) {
		string methodName = context.Request.Params["methodName"];
		if (!string.IsNullOrEmpty(methodName)) {
			HandlerMapSection settings = 
(HandlerMapSection)ConfigurationManager.GetSection(HandlerMapSection.SectionName);
			if (settings != null) {
				HandlerMap map = settings.Maps[methodName];
				if (map != null) { BaseHandler handler = 
(BaseHandler)_currentAssembly.CreateInstance(map.TypeName, false, 
BindingFlags.CreateInstance, null, new object[] { context }, 
System.Globalization.CultureInfo.CurrentCulture, null);
					return handler;
				}
			}
		}
		return null;
	}
}

To add a new method we just create a new class inherited from BaseHandler and make an addition to our config file as follows.

<configSections>
    <section name="handlerMapping" type="HandlerMapSection"/>
  </configSections>
  <handlerMapping>
    <map methodName="getCustomerList" typeName="CustomerListHandler"/>
    <map methodName="Customers" typeName="CustomerHandler"/>
    <map methodName="Feeds" typeName="FeedsHandler"/>
  </handlerMapping>

Paging the RSS JSONP Feeds

One of the things i needed to add was paging through a record set for the JSONP feeds. By simply clicking the Prev and Next buttons you can page back and forward.

public static class DataHelper
{
	private static readonly string connRSSFeeds = 
	ConfigurationManager.ConnectionStrings["RSSFeeds"].ConnectionString;
	private static readonly string connNorthwind = 
	ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;

	public static List<rssfeed> GetFeeds(string cat, int start, int max)
	{
		List<rssfeed> list = new List<rssfeed>();
		const string SQL = "SELECT  [FeedId],							  " +
		"		[category],											   " +
		"		[title],												  " +
		"		[author],												 " +
		"		[link],												   " +
		"		[shortDescription],									   " +
		"		[description],											" +
		"		[image],												  " +
		"		[publishedDate],										  " +
		"		[rank]													" +
		"FROM															  " +
		"	 (															" +
		"		SELECT  [FeedId],										 " +
		"				[category],									   " +
		"				[title],										  " +
		"				[author],										 " +
		"				[link],										   " +
		"				[shortDescription],							   " +
		"				[description],									" +
		"				[image],										  " +
		"				[publishedDate],								  " +
		"				[rank],										   " +
		"				ROW_NUMBER() OVER (ORDER BY [rank] DESC,		  " +
		"					 [publishedDate] DESC) AS [RowIndex]		  " +
		"		 FROM [RSSFeeds].[dbo].[Feeds] WHERE ([category] = '{0}') " +
		"	 ) AS [RSSFeedsWithRowIndex]								  " +
		"WHERE   ([category] = '{0}')									  " +
		"AND	 ([RowIndex] > {1})										" +
		"AND	 ([RowIndex] <= ({1} + {2}))							   ";

		if (max <= 0) { max = 50; }
		List<string> feeds = new List<string>();
		using (IDbConnection cnn = CreateConnection(connRSSFeeds)) {
			using (IDbCommand cmd = cnn.CreateCommand()) {
				cmd.CommandText = string.Format(SQL, cat, start, max);
				using (IDataReader rdr = cmd.ExecuteReader()) {
					while (rdr.Read()) {
						RSSFeed f = new RSSFeed();
						f.FeedId = rdr.IsDBNull(0) ? Guid.Empty.ToString() 
									: Convert.ToString(rdr.GetGuid(0));
						f.category = rdr.IsDBNull(1) ? string.Empty 
									: rdr.GetString(1).Trim();
						f.title = rdr.IsDBNull(2) ? string.Empty 
									: rdr.GetString(2).Trim();
						f.author = rdr.IsDBNull(3) ? string.Empty 
									: rdr.GetString(3).Trim();
						f.link = rdr.IsDBNull(4) ? string.Empty 
									: rdr.GetString(4).Trim();
						f.shortDescription = rdr.IsDBNull(5) ? string.Empty 
									: rdr.GetString(5).Trim();
						f.description = rdr.IsDBNull(6) ? string.Empty 
									: rdr.GetString(6).Trim();
						f.image = rdr.IsDBNull(7) ? string.Empty 
									: rdr.GetString(7).Trim();
						f.publishedDate = rdr.IsDBNull(8) ? string.Empty 
									: rdr.GetDateTime(8).ToLongDateString();
						f.rank = rdr.IsDBNull(9) ? int.MinValue 
									: rdr.GetInt32(9);
						list.Add(f);
					}
				}
			}
		}
		if (list.Count == 0) { return null; }
		return list;
	}
</string></string></rssfeed></rssfeed></rssfeed>

Paging the Northwind Database

In addition, I also included a sample of one method to page through the customer records in the Northwind database in a PhoneGap Cordova Mobile App. By simply clicking the Prev and Next buttons you can page back and forward. This assumes that you already have the Northwind database installed on your server.

Our AJAX Call Isn't To Google!!

I can understand why Google cahches requets but for many of the mobile apps I created for clients this wasn't acceptable. So I decided to use a Handler and JSONP to create my own RSS Feeds on my own server.  Just as a note, keep in mind that in a mobile app where te user presses a button to initiate a feed request the volume of requests will be substantially less than where an app would make such a request whenever it is turned on. And where that is the case this simple approach presented has worked nicely.

function GetRSSAds() {
    var rss_ads = store.get('rss_ads');
    if (typeof rss_ads == 'undefined') {
      store.set('rss_ads', {
        cat: 'new',
        start_index: 0
      });
      rss_ads = store.get('rss_ads');
    }
    if (rss_ads.cat.length < 1) {
      store.set('rss_ads', {
        cat: 'new',
        start_index: 0
      });
      rss_ads = store.get('rss_ads')
    }
    var _cat = rss_ads.cat;
    var _start = rss_ads.start_index;
    var _max = 50;

    <span class="style4">////////////////////////////////////</span>
<span class="style4">    // NOTE: We are NOT using Google !!!</span>
<span class="style4">    ////////////////////////////////////</span>
    //var url = 'http://www.your_website.com/rsshandler.ashx?cat=' + _cat + '&start=' + _start + '&max=' + _max + '&methodName=Feeds&jsonp=onRSSLoaded';
    var url = 'rsshandler.ashx?cat=' + _cat + '&start=' + _start + '&max=' + _max + '&methodName=Feeds&jsonp=onRSSLoaded';
    $.ajax({
      type: 'GET',
      url: url,
      async: false,
      contentType: "application/json",
      dataType: 'jsonp'
    });
  }

The RSSFeeds Database & Feeds Table

The table below created with the SQL script for this article shows how we can create a category in a category. If we put the word "category" in the category field itself then we will load a list of categories and in the link field if we have say, "#category|movies" then clicking on the "Go" button in the scrolling list will load all items from this same table with "movies" in the category field. Of course, you can customize this any way you want. What is include here is only one way of doing this.

The iOS7 Frosted Panel Look

To create an iOS7 Frosted Panel Look there are 3 "tricks" I used, namely:

  • Z-index of panel is -1. Prevent controls from being blurred

  • Panel's Background is transparent

  • Blur what is under the panel

In order to blur or frost what is under the sliding panel I used 2 classes, namely, backfrost_on and backfrost_off , that I add and remove to " scroller_player " which is the <div> tag that holds the screen content.

$("#panel_controls").on("panelbeforeopen", function (event, ui) {
		$('#scroller_player').removeClass('backfrost_off');
		$('#scroller_player').addClass('backfrost_on');
	});

	$("#panel_controls").on("panelbeforeclose", function (event, ui) {
		$('#scroller_player').removeClass('backfrost_on');
		$('#scroller_player').addClass('backfrost_off');
	});

	.frosted::after {
		<span class="style4">/* z-index:-1 Only blurs background, NOT controls on panel */</span>
		<span class="style4">z-index: -1 !important;</span> 
		position: absolute;
		content: "";
		right: 0;
		top: 0;
		width: 100%;
		height: 100%;
		background: rgba(255,255,255,0.5);
		-webkit-filter:blur(8px) brightness(110%);
		-moz-filter: blur(8px);
		-ms-filter: blur(8px);
		-o-filter: blur(8px);
		filter: blur(8px); 
	 }

	 .backfrost_on {
		 -webkit-filter:blur(2px) brightness(110%);
		 -moz-filter: blur(2px);
		 -ms-filter: blur(2px);
		 -o-filter: blur(2px);
		 filter: blur(2px); 
	 }
	 .backfrost_off {
	 }

Some Final Thoughts

Custom RSS Feeds alow you to create with minimal code dozens of different theme apps and to deliver to users information and video ads that don't interfere with AdMob ads. There are many ways to implement custom JSONP and this article is intended to illustrate just one of those ways for PhoneGap Cordova apps.





About List