Download Firefox -  a safer, easier to use web browser. Return to iribbit.net - Leap into the online experience! Return to iribbit.net - Leap into the online experience! iribbit.net - Leap into the online experience!

Project News :.

The latest project to launch was the site for Gorilla Offroad Company. Aside from their main site, a social media strategy was develop to launch the company into various industry specific automobile enthusist discussion board communities as well as popular social media fronts like Facebook, Pinterest, and Twitter.


Valid XHTML 1.0 Transitional

Valid CSS!

Section 508 Compliant

powered by: Macromedia ColdFusion MX

made with: Macromedia Dreamweaver MX

What is RSS

XML - often denotes RSS Feed information.

Macromedia - ColdFusion Programming
white horizontal rule

ColdFusion News :.

To bring a little life to my site, I've pulled a couple What is RSS Feeds into this page. You can currently choose between the technology related news stories from the following news sources:



You are currently viewing and RSS Feed from Raymond Camden's Blog.



My perspective of working with the Ionic Framework

For a while now I've been praising the the Ionic framework as one of the coolest things to happen to Cordova/PhoneGap development. I kept promising to talk about it a bit more deeply on the blog and today I've finally gotten around to it. This will be somewhat long, and rambling, but I hope it will give readers an idea of why Ionic is so cool and why they should consider giving it a try.

Let's begin by talking about what the framework actually is. I'll steal their marketing tagline as a jumping off point:

Free and open source, Ionic offers a library of mobile-optimized HTML, CSS and JS components for building highly interactive apps. Built with Sass and optimized for AngularJS.

Ok, cool, so let's break that down a bit.

First and foremost, Ionic is not just another UI framework. Not that there is anything particularly wrong with that; I love UI frameworks as they are a simple way to make my demos look nicer, but Ionic is much more than this. To be clear, there is a UI part to the framework and in theory you could use just that aspect, but you would be missing out on a significant part of what makes Ionic great. You can see plenty of examples on the site itself, but here are a few samples I stole from the docs. First, a Card view:

And here is a list view:

You can see more examples on their components page. Basically - a nice clean, perhaps Bootstrap-ish, look to it.

The next major aspect of Ionic is that it is "optimized" for Angular. When you work with projects (and I'll talk a bit more about that experience later), your initial code will be an Angular project, so if you are using Ionic, you can pretty much assume Angular as well. Do not let that be a blocker for you. If you read my blog regularly, then you know I've recently started working with Angular again. I'm very much at the newbie level where I can get stuff done, slowly, and the code is ugly, but it works, and that makes me happy. So speaking as an Angular noob, I felt comfortable using Ionic with it. What problems I did have came more from my lack of experience and never actually stopped me from working. Of course, if you know nothing of Angular, you may want to spend a few minutes looking at it before you use Ionic, but you certainly do not need to be an expert.

Ionic provides multiple different directives for your Angular app, making those nice UI things like the cards and lists somewhat easier to use. Oddly, these are found in the JavaScript portion of the docs, which doesn't quite make sense to me, but that's where you'll find them. There are also times when the docs about the directives are not quite as clear as the pure CSS side. As an example, when I looked at the documentation for ion-list, it wasn't clear how I was supposed to build an inset list. The CSS docs made it clear, but I didn't get how to translate that to the directive version. The solution was pretty simple (add class="list list-inset" to the directive), but it seems like that should have been more clear.

On top of the UI support, there is also strong UX support as well. By that I mean common UX metaphors like pull to refresh are easy to add (ion-refresher) via directives. Other examples include scrollable panes and even my favorite (not), infinite scroll. Basically, all the "typical" UX things you may need in your app are baked into the framework and are pretty easy to use, again, even with me being new to Angular.

The final major aspect of Ionic is its use as a platform to build Cordova (PhoneGap) applications. Again, you don't need to use it for that, but the main Ionic tool (the CLI) is wrapped around working with Cordova projects. You can, of course, skip this and use the UI/UX components in a mobile-optimized web page or in some other hybrid application, but the real treat here is for Cordova developers. When you mix this with the sister project ngCordova, the combination becomes even more powerful.

Ionic has a CLI that can be used to create a new project. It also wraps Cordova CLI commands. So for example, I can start a project with the Ionic CLI and work with my platforms and plugins as well. I can also fire off a call to the emulator from it.

Now - my first impression of this was that it was nice to have a good way to seed a new Ionic project, but that I probably wouldn't use it much after that. On a whim though I took a look at the ionic serve command. Cordova has a similar command. The idea is that it fires up a HTTP server so you can test your code in a desktop browser. That's handy. But there are a number of problems with this feature. First off - you can't use the "core" www as is. You must do a build first to copy assets into an appropriate platform folder. You can get around this with Grunt of course (see my example), but it isn't necessarily ideal.

Ionic's serve command handles this much better in my opinion. First off - it allows you to edit code in www and see it reflected immediately. And by immediately, I mean immediately. Upon running ionic serve ios, the CLI will actually open a new tab for you, load your app, and constantly monitor your file system for changes. As soon as you edit something, it automatically reloads the tab. Again, this is all stuff you could do with Grunt, but Ionic has it out of the box.

So, that's a lot of talk about what Ionic is. Now let me switch gears and talk about the project I built. Please note that I'm attaching the entire project as a zip attachment to this blog entry. But more important than that, please remember I am new to Angular and Ionic! What you will see here is most likely not optimal code!

For my project I decided to rebuild my INeedIt application. This is an application I've built a few times now. First in Flex Mobile. Then Backbone, and most recently in Angular back in January of this year. The application is rather simple. It figures out your location and then uses the Google Places API to let you know what businesses are nearby. My most recent version used Ratchet for a UI framework. Since the previous version was built in Angular, I was curious how much would directly port over and how much I'd need to tweak for Ionic.

For the most part, my Controller layer did not change at all. I did switch to using a StateProvider, which isn't an Ionic thing but an Angular thing, and while a bit weird at first, it kind of makes sense now. Smarter folks than me said this is the "right" way to do routing in Angular and I'm fine with that.

The real changes were at the view level. I removed all calls to Ratchet's CSS and began building as much as I could with Ionic. In general, this wasn't a big deal. As I mentioned above, sometimes it was a bit difficult to figure out the right CSS arguments for a directive. Also, Ionic has bugs. Shocking, I know, but for example, my list view needed to include some additional code to work around inset lists being broken. Here is that list. Note the div essentially replicates the ion-list tag.

<ion-view title="INeedIt">
	
	<ion-content>
		<div class="list list-inset">
		<ion-list class="list list-inset">

			<ion-item ng-repeat="service in services" href="#/service/{{service.id}}">
				{{service.label}}
			</ion-item>

		</ion-list>
		</div>
	</ion-content>	

</ion-view>

Of course, the nice thing about working within Angular is that as soon as this particular bug is fixed, it will be trivial for me to yank it out. Also, I was able to use the Ionic Support Forum to quickly find the issue and the workaround. I had a few problems while working on my application and the response on the forum was - on average - very fast and polite. Heck, even when my issues were Angular-based, not Ionic-based, I got great support. (Not that I'd abuse it, but it is nice to know that the folks there recognize that developers like me may run into issues that subtly cross the line from their responsibility to general Angular issues.)

Let's take a look at the screens. The initial screen is temporary while your location is loaded. After that a simple list of services is provided.

After selecting the service type, the Places API will then fetch results based on your location. Yes, these are real restaurants located near me.

When a business is selected, I show you details of the location. I decided to make use of Ionic's card view here and a cool slider gesture for pictures. Here is an example.

If you scroll down a tiny bit, you'll see that you can swipe those pictures left and right to see more of them.

How difficult was that part? Check out the code below.


<ion-slide-box show-pager="true">

    <ion-slide ng-repeat="photo in place.photos">
        <img ng-src="{{photo.url}}">
    </ion-slide>

</ion-slide-box>	

Yes, that simple. I love that!

So, long story short, after being impressed with Ionic, I was happy to get a chance to build a "real", if simple, project. And while I definitely ran into hiccups, I'm more impressed now. I strongly urge folks to give it a shot and let me know what you think in the comments below.


(Mon, 28 Jul 2014 11:36:00 -0400)
[view article in new window]

Sunday OT - Wolfenstein: The New Order

I've been a fan of the Wolfenstein series for quite some time, and when I say "quite some time", I'm talking a long time. I remember playing the original Wolfenstein, back before it got fancy with 3D:

Because of my long history with the game, I was really looking forward to trying the latest incarnation, and especially interested in seeing how a FPS performed on the PS4. I was not disappointed.

Graphically and gameplay wise, the game is fun as hell. It has great balance, with only a few areas being out of sync difficulty wise with the others, and I found myself going to Google only for the very last boss battle. (And frankly, if I had just freaking looked up, it would have been pretty obvious what I needed to do.) The play is a bit similar to Gears of War in terms of making wise use of cover, and there is a stealth element present as well. But like Dishonored, it is somewhat optional. If you feel like being stealthy you can be, but if you want to run in guns blazing, that's an option as well. Even though I stuck to a few main guns, the variety available and the upgrades made the selection fun. I'm still not quite sure how they got a rocket launcher on an assault rifle, but I'm perfectly fine just accepting that.

The story is what really sold me though. I am a huge Alt-History fan, and while "The Nazis won" is kind of a trite example of the genre, in the game it works very well, especially mixed in with the sci-fi elements. As you play you come across various newspaper clippings and other items that give you a look at the history of the game and I made sure to read each and every one.

Probably the only negative I can think of is that there is no multiplayer, but honestly, I don't do much multiplayer nowadays anyway so it doesn't bother me much. It is good to see a game focused on single player for once! The game lasted about twenty hours for me, which is a good long time, and there is actually a valid reason to replay the entire game (more than just - "find all the hidden crap"). I won't say why as it is somewhat of a spoiler, but I've already started my second play through.

Sound good? Buy it via the link below and I get like a dollar or so that I'll put to my next video game.


(Sun, 27 Jul 2014 07:56:00 -0400)
[view article in new window]

Updated/Relaunched site - Popular Followers

Almost two years ago I announced the launch of a new site. It was built to display a report of your followers sorted by how many followers they had. Now - I completely recognize that this isn't necessarily an important report. But it was something I was curious about and I thought it would be cool. I'm being followed by two stars of Young and the Restless (my secret obsession), one of my favorite authors, and best of all, Game of Thrones.

Unfortunately, Twitter killed off almost all the public APIs it has and now requires a heck of a lot more work (comparatively so anyway) to use their API. Why? I assume to help keep their servers up. Either that or Twitter hates simple. (And kittens.) A while ago I decided to take a stab at rebuilding it as a server-side application in Node.js. I found an awesome article on it (How To Use OAuth and Twitter in your Node.js / ExpressJS App) and was quickly able to add OAuth to my application.

Unfortunately, even with access to the API now what I wanted to do still wasn't necessarily easy. I begin by getting the IDs of your followers. That's simple enough although it maxes out at 5000 at a time. But then I need to get details about your followers. You are allowed to get 100 at a time. So for someone like me, nearing 8000 followers, that's about 80 calls. It is still within the API limits (since it should be per user using OAuth), but it is approaching the max. For folks curious, here is the code I used just for this process.

function getIds(screen_name, sess, cb, data, start) {
	if(!data) data=[];
	if(!start) start = '-1';

	var oa = new OAuth(sess.oa._requestUrl,
	                  sess.oa._accessUrl,
	                  sess.oa._consumerKey,
	                  sess.oa._consumerSecret,
	                  sess.oa._version,
	                  sess.oa._authorize_callback,
	                  sess.oa._signatureMethod);
	
	oa.get('https://api.twitter.com/1.1/followers/ids.json?cursor='+start+'&screen_name='+screen_name+'&count=5000&skip_status=1', sess.oauth_access_token, sess.oauth_access_token_secret,            
      function (e, retData, ores) {
		if (e) {
			console.log('getIds: error result');
			console.dir(JSON.parse(e.data));
			/*
			var error = JSON.parse(e.data).errors;
			res.send({error:1, message:error[0].message});
			*/
		} else {
			console.log('get ids done');
			retData = JSON.parse(retData);
			data = data.concat(retData.ids);
			//console.dir(data);
			if(retData.next_cursor) {
				getIds(screen_name, sess, cb, data, retData.next_cursor);	
			} else {
				cb(data);
			}
		}
      });
}

function getFollowers(sess, ids, cb, data, start) {
	if(!data) data=[];
	if(!start) start = 0;
	var idSlice = ids.slice(start,start+100);
	var sn = idSlice.join(',');
	var oa = new OAuth(sess.oa._requestUrl,
	                  sess.oa._accessUrl,
	                  sess.oa._consumerKey,
	                  sess.oa._consumerSecret,
	                  sess.oa._version,
	                  sess.oa._authorize_callback,
	                  sess.oa._signatureMethod);
	
	oa.get('https://api.twitter.com/1.1/users/lookup.json?user_id='+sn, sess.oauth_access_token, sess.oauth_access_token_secret,             
      function (e, retData, ores) {
		if (e) {
			console.log('getFollowers: error result');
			console.dir(JSON.parse(e.data));
			
			var error = JSON.parse(e.data).errors;
			cb({error:1, message:error[0].message});			
		} else {
			retData = JSON.parse(retData);
			console.log('got '+retData.length+ ' items');
			
			//All we need is username + followercount
			for(var x=0, length=retData.length; x<length; x++) {
				data.push({screen_name:retData[x].screen_name, followers:retData[x].followers_count,profile_image_url:retData[x].profile_image_url});
			}
			//data = data.concat(retData);
			console.log('size of data is now '+data.length + ' and ids len is '+ids.length);
			//console.dir(data);
			if(data.length < ids.length && data.length < 10000) {
				getFollowers(sess, ids, cb, data,start+retData.length);	
			} else {
				cb(data);
			}
		}
      });
	
};

Note I use a simple memory-based cache so it doesn't have to refetch the data on reload. Speaking of - there is a bug currently with the site where the request will time out on the client for people (again, like me) with lots of followers. In fact, it always does this for me on my first load, but just on the production server. When I reload, the server returns the data immediately as it apparently kept churning away on the back end. This app is not perfect yet, and probably never will be. I built it for fun.

So, want to check it out? Just head over to popularfollowers.com and give it a spin.


(Thu, 24 Jul 2014 09:38:00 -0400)
[view article in new window]

An example of Cordova's Camera PopoverOptions

One of the interesting features in Cordova's Camera API is something called popoverOptions. While the docs do explain what this feature does, it may not be entirely clear how it works so I whipped up a quick example, with screen shots, to demonstrate what it does.

First off - this feature is only for iOS (and the iPad at that) and only for existing pictures, it is not something you can use when having the user take new pictures. So right away you can see this is something that will have limited usage. But what exactly does it do? The docs say:

iOS-only parameters that specify the anchor element location and arrow direction of the popover when selecting images from an iPad's library or album.

The options include:

  • X and Y values to anchor the popover. This makes sense.
  • Width and height for the anchor. This didn't quite make sense. My take is - if you are binding the popover to a UI item, then you specify this so iOS knows how big the anchor is, and combined with x and y, it gives it enough data to know how to position it.
  • An arrowDir property that specifies what direction the popover will be from the anchor. So if you specify a right arrow, the popover will be on the left. Most likely you will use the "any" option to let iOS figure it out.

Let's look at a simple example. I built a very simple HTML page that includes a button for prompting the user to select an image as well a blank image (styled red) that will serve as my anchor. Here is the HTML.

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
		<title></title>
		<meta name="description" content="">
		<meta name="viewport" content="width=device-width">
        <link rel="stylesheet" type="text/css" href="css/app.css" />
	</head>
	<body>

	<img id="canvas" />
	<button id="startCamera">Start Camera Test</button>

	<script src="cordova.js"></script>	
	<script src="js/app.js"></script>
	</body>
</html>

I used a bit of CSS to specify the position and size of the image:


body {
	margin-top: 20px;
}

#canvas {
	position: absolute;
	left: 300px;
	top: 300px;
	background-color: red;
	width: 200px;
	height: 200px;
}

Now let's look at the JavaScript code.

document.addEventListener("deviceready", init, false);
function init() {
	document.querySelector("#startCamera").addEventListener("touchend", startCamera, false);
}

var cameraPopoverHandle;

function startCamera() {

	cameraPopoverHandle = navigator.camera.getPicture(onSuccess, onFail,
     { destinationType: Camera.DestinationType.FILE_URI,
       sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
       popoverOptions: new CameraPopoverOptions(300, 300, 200, 200, Camera.PopoverArrowDirection.ARROW_ANY)
     });

}

function onSuccess(u) {
	console.log('onSuccess');
	document.querySelector("#canvas").src = u;
}

function onFail(e) {
	console.log('onFail');
	console.dir(e);
}

 // Reposition the popover if the orientation changes.
 window.onorientationchange = function() {
 	console.log('running onorientationchange');
	var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY);
    cameraPopoverHandle.setPosition(cameraPopoverOptions);
 }

Basically, all we have here is a touch handler for the button that fires off a call to the Camera API. Notice we passed in popover options. The X, Y, Width, and Height attributes are based on the CSS I used for the image. Finally, there is a bit of code to handle orientation changes that I took right from the docs. More on that in a minute. So let's look at this in action.

Note how the popover is anchored to my image DOM element. Note too how it is positioned automatically. Once an image is selected, the popover automatically goes away and the rest of my code is fired.

That's mostly it. It's a simple change and it does work better for tablets. You would need to add a bit of logic to see if this is an iPad. Remember that the Device API makes this trivial. Although in my testing it didn't really matter. I switched to the iPhone and the popover options were simply ignored.

Now let's talk about the orientation change. I got this right from the docs and it certainly makes sense, but oddly, it seems as if the X,Y values are ignored. Based on the code from the docs, it seems like it should reposition to 0,0 and assume an anchor size of 100,100. I should have used better values for that, but I wanted to test it as is at first. In my testing, these values seemed to have no impact. In fact, if I remove the code completely, iOS still handles the orientation change ok. If I get some clarification on why this is I'll update the code accordingly.

As before, I've put this up on GitHub in case you want to quickly test: https://github.com/cfjedimaster/Cordova-Examples/tree/master/popovertest


(Wed, 23 Jul 2014 08:28:00 -0400)
[view article in new window]

PhoneGap Day US and EU Announced

For a few years now the PhoneGap team have been running a one day conference called PhoneGap Day. This is easily one of my favorite conferences. It has a very laid back vibe. The sessions are short and sweet (20 minutes or so a pop). The schwag is actually useful. Out of all the conferences I attend, this one just feels the most relaxed, fun, and educational as well.

This year PhoneGap Day will be held in San Francisco on October 24th and Berlin on September 26th. As before, there will be full day workshops on the day before. For more information, visit the web page: http://pgday.phonegap.com/

Want to speak? The call for speakers is open as well. I've submitted one topic and plan on submitting another.


(Tue, 22 Jul 2014 14:40:00 -0400)
[view article in new window]

Targeting a device type with Cordova Emulate

Today I've got yet another Cordova tip that was probably known to most people. I'm working on a sample application that needs to run on an iPad, not an iPhone, but the problem was that every time I ran cordova emulate ios, it would fire up the application in an iPhone. If I switched the hardware in the iOS simulator settings it would work, but when I recompiled and reran, it reverted to an iPhone. Turns out, there is a simple solution for this.

I had never noticed this, but cordova emulate is simply an alias for cordova run --emulator. If you run cordova at the command line with no arguments, you will see a few options for the run command. These options include device and target. If you check the docs for the CLI, it doesn't specify what these do. To be honest, I was a bit confused at first.

The --device flag is simply a way to say that you want to run the code on a device. Which is what cordova run means anyway, but the flag is there (as far as I can tell) for completeness sake to be the corollary to the --emulator flag.

The --target flag is the interesting one. This too isn't documented, but if you open up projectroot/platforms/ios/cordova/run, it is documented in the shell script.


# Valid values for "--target" (case insensitive):
#     "iPhone (Retina 3.5-inch)" (default)
#     "iPhone (Retina 4-inch)"
#     "iPhone"
#     "iPad"
#     "iPad (Retina)"

Pretty simple, right? So for my testing, the solution was simple: cordova emulate ios --target="iPad". Worked like a charm. I also looked into the Android folder and while there wasn't a simple list, from what I can see you can pass the name of an AVD. So for example, I've got an AVD called KitKat, so I was able to do: cordova emulate android --target=KitKat and it fired up the bits in that particular emulator. (Although I'm sure as hell trying to avoid the emulator now that I've got Genymotion working well.)

p.s. Thanks to devgeeks and shazron in IRC for helping me with this post!


(Mon, 21 Jul 2014 15:38:00 -0400)
[view article in new window]

ColdFusion, isValid, Email and new TLDs

A few days ago a user reported an issue with my blog involving the comment form. Apparently he has an email address using one of the new TLDs (top level domains) that are cropping up, specifically "directory." I decided to do some testing to see how well ColdFusion supports these new TLDs.

First off, it was a bit difficult to find out what has been added recently, but I did find a Wikipedia page with everything listed: ICANN-era generic top-level domains. I knew new TLDs were coming, but my god, I had no idea how many and how... weird some of them were. I mean, I guess it is kind of cool that "blue" is a TLD. But... ok, whatever.

I decided to write a quick test script that would use isValid against some of these new TLDs. I wasn't going to try to type them, just a sample. Here is the script.

<cfscript>
tlds = "com,edu,directory,guru,gift,jobs,international,museum,name,sexy,social,tel,travel,ceo,cheap";

for(i=1; i<=listLen(tlds); i++) {
	emailToTest = "foo@foo.#listgetAt(tlds, i)#";
	writeoutput("Email: #emailToTest# isValid? #isValid('email',emailToTest)#<br>");
}

</cfscript>

As you can see, it just a simple list of TLDs. I iterate over them, create a test email address, and run isValid against it. Here are the results:

Email: foo@foo.com isValid? YES
Email: foo@foo.edu isValid? YES
Email: foo@foo.directory isValid? NO
Email: foo@foo.guru isValid? YES
Email: foo@foo.gift isValid? YES
Email: foo@foo.jobs isValid? YES
Email: foo@foo.international isValid? NO
Email: foo@foo.museum isValid? YES
Email: foo@foo.name isValid? YES
Email: foo@foo.sexy isValid? YES
Email: foo@foo.social isValid? YES
Email: foo@foo.tel isValid? YES
Email: foo@foo.travel isValid? YES
Email: foo@foo.ceo isValid? YES
Email: foo@foo.cheap isValid? YES

So most of them passed, but a few, like directory and international, did not. I couldn't figure out why until I noticed that both were a bit long. Then I figured it out. ColdFusion was simply checking the length of the TLD. As a test, I tried "abcdefg" as a TLD and it worked. As soon as I tried "abcdefgh", it failed. I'm going to report this as a bug.

As it stands, this blog uses a UDF to check for email validity. (The code began back in ColdFusion 6, so I've got a lot of skeletons in my code closet.) The UDF I'm using uses regular expressions and uses a TLD checker of "Either 2-3 characters or in this hard coded list." Here is the code now:

function isEmail(str) {
return (REFindNoCase("^['_a-z0-9-]+(\.['_a-z0-9-]+)*(\+['_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*\.(([a-z]{2,3})|(aero|asia|biz|cat|coop|info|museum|name|jobs|post|pro|tel|travel|mobi))$",arguments.str) AND len(listGetAt(arguments.str, 1, "@")) LTE 64 AND
len(listGetAt(arguments.str, 2, "@")) LTE 255) IS 1;
}

My thinking is that I'll just modify that first clause in the TLD section to allow for 2 to 30 characters, with 30 being pretty arbitrary. I'm open to suggestions!


(Mon, 21 Jul 2014 08:39:00 -0400)
[view article in new window]

Soundings Update

So hey, remember I built an open source ColdFusion survey application? This morning someone sent in a good request and I quickly added it. Surveys may now have an "attachment" question. Basically it means you can create a survey that also asks people to upload an attachment.

You can grab the latest bits on GitHub: https://github.com/cfjedimaster/Soundings.


(Wed, 16 Jul 2014 11:00:00 -0400)
[view article in new window]

Cordova Sample: Reading a text file

A few weeks back I began a list of questions to help build a PhoneGap/Cordova File System FAQ. (More on that at the very end.) As I work through the questions I'm building little samples (like this one) to help demonstrate various FileSystem features. Today's is really simple, but as always, I figure people can find this helpful even if it trivial. (And if I'm wrong, let me know in the comments below.) Today's example simply reads a text-based file from the file system and displays it in the application.

Let's take a look at the code and then I'll walk you through what it does.

document.addEventListener("deviceready", init, false);
function init() {
	
	//This alias is a read-only pointer to the app itself
	window.resolveLocalFileSystemURL(cordova.file.applicationDirectory + "www/index.html", gotFile, fail);

	/* Yes, this works too for our specific example...
	$.get("index.html", function(res) {
		console.log("index.html", res);
	});
	*/

}

function fail(e) {
	console.log("FileSystem Error");
	console.dir(e);
}

function gotFile(fileEntry) {

	fileEntry.file(function(file) {
		var reader = new FileReader();

		reader.onloadend = function(e) {
			console.log("Text is: "+this.result);
			document.querySelector("#textArea").innerHTML = this.result;
		}

		reader.readAsText(file);
	});

}

After deviceready fires, we use resolveLocalFileSystemURL to translate a path into a FileEntry object. Once again I'm using one of the aliases that ship with the latest version of the File plugin: cordova.file.applicationDirectory. As you can probably guess, this points to the application itself and is read-only, which is fine for our purposes. To this path I add www/index.html to refer to the index.html of the app itself.

As you can see in the commented out code, if I really did want to read something inside the www folder, I could just use Ajax. The example in the comment is jQuery-based. But since I wanted to demonstrate the FileSystem API I use it instead. For something this simple I'd probably just use Ajax typically.

Once we get the FileEntry object we can then run the file method on it. This gives us a handler to the file itself (think of FileEntry as the agent for the movie star file that is too busy to give us the time of day). Once we have that, we then fire up a new FileReader object, run readAsText on it, and wait for the read operation to end. Once it is done we have access to the contents of the file and can do - whatever - with it. In the sample app I simply added a text area to the DOM.

That's it. Full source may be found here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/readtextfile.

As a quick FYI, I have begun work on the FAQ itself. You can view (and comment) on the Google doc here: https://docs.google.com/document/d/1qKB63z3U2BwCl7Gc-Ry7cPbNbQB-Cur2BaS1BRB1tV0/edit?usp=sharing


(Tue, 15 Jul 2014 13:51:00 -0400)
[view article in new window]

Yet another Cordova/PhoneGap Debugging Tip

Want another way to debug Cordova/PhoneGap apps? This isn't new, but I tend to forget about this option and it came in handy yesterday so I thought I would share. When you send builds to the simulator/device via the command line, you may notice that at the end of all the output about building this and generating that, you get these two lines:

2014-07-14 17:23:36.846 ios-sim[1335:507] stderrPath: /Users/ray/readtextfile/platforms/ios/cordova/console.log
2014-07-14 17:23:36.847 ios-sim[1335:507] stdoutPath: /Users/ray/readtextfile/platforms/ios/cordova/console.log

What this is telling you is that you have a log file that will report on errors from your application. This also includes console.log output. (As well as console.dir.) If you simply tail -f this file in another terminal tab (and yes, you get tail -f in Windows too with the right download), you get your console output as plain text right in your terminal. Here is an example from what I was working on yesterday.

Edit on July 16: As just an FYI, when I just tested this on a new app this morning, it did not work. I had to add the Console plugin. My initial test was with a Ionic app where this was added automatically.


(Tue, 15 Jul 2014 09:35:00 -0400)
[view article in new window]

Verified plugins site for Cordova applications

Currently the main site I use for finding Cordova plugins is plugins.cordova.io. Last week Telerik announced the creation of a new site, the Verified Plugins Marketplace. This site contains plugins that have been tested to ensure they work well and contain sample apps written with Kendo UI. I've often said that as a new Node developer, the availability of npm modules is both good and overwhelming as well. Having a curated, filtered list of plugins could be a huge benefit for Cordova/PhoneGap developers.


(Mon, 14 Jul 2014 13:39:00 -0400)
[view article in new window]

First release of Cordova Brackets extension

I blogged about this a few days ago, but I think I'm ready to really release my Cordova Brackets extension. The code is pretty much crap and it's really lacking in providing good feedback while it works, but the initial feature list is complete. Assuming you've got the Cordova CLI installed already, you can, via Brackets:

  • Add and remove platforms. Warning - if Cordova needs to grab the bits for the platform (it does this once), it will be slow, and again, my extension isn't providing feedback about the operation until it is done, so, like, don't do anything. Go get coffee.
  • Emulate for a platform.
  • Not use run. It doesn't work, which means I kinda lied when I said the initial feature list was complete, but I'm OK with that.
  • List plugins, along with the version.
  • Add plugins, and I've got a nice autocomplete so you can quickly find the right plugin.
  • Remove plugins too. There are bugs around this, so, be careful.

You can see this in action in this thrilling video below. Oddly it is a bit out of focus for the first few seconds, but then it clears up. I blame gremlins.

It is now available via the Extension Manager so you can install it directly from within Brackets. If you want to help work on the source, pull a fork over at https://github.com/cfjedimaster/Cordova-Extension. If you really like it, visit the Amazon wishlist and pick up that Marvel Lego game. ;)


(Fri, 11 Jul 2014 16:17:00 -0400)
[view article in new window]

Issue with CFINDEX

If you use cfindex to parse a directory of files, you should be aware of a serious issue that may hit you. As you know, you can ask cfindex to return a result structure that tells you how many files were added, removed, or deleted from an index. As an example, here is the result structure for a set of PDFs I added.

Cool, right? Except that I had more than one PDF in the directory. So, something went wrong, right? But the issue is that there is no feedback about what went wrong. I did some digging into the logs and didn't find anything. I was truly at a loss as to what it could be. Every PDF opened up in Acrobat so they were definitely valid PDFs, and of course, my assumption was that if they weren't valid PDFs I would have certainly gotten some feedback.

I was wrong.

So, luckily, I got some help from Uday Ogra on the ColdFusion team. Turns out it was logged, just not in the log I expected. When I checked server.log, I saw this:

WARNING: Could not index /lotsofpathstuff/pdftest/temp/I-9.pdf in SOLR. Check the exception for more details: An error occurred during EXTRACTTEXT operation in <CFPDF>.

I quickly wrote a new CFM file that used cfpdf and the extracttext operation and confirmed that the error indeed made sense for the PDF in question. I opened the PDF in Acrobat, checked the document properties, and confirmed that it was blocked.

Ok, all of this makes sense then. But the issue is that there was no feedback given at the request time about a document failing or why it failed. Unfortunately, this to me is a feature killer. To be clear, I'm not talking about ColdFusion Solr integration as a whole, but if your business process involves indexing dynamic files then you can't rely on knowing exactly what files are failing. Of course, they won't always fail. In theory you could replace the "index the directory" operation with indexes of each and every file one by one. As you don't get an exception you would need to check the status to see if the numbers add up. That's a workaround, but a poor one in my opinion.

I filed a bug report on this issue where I proposed that the result struct return an array of errors. Each item in the array (if any exist) would consist of the full path to the file in question as well as the exception thrown. Adam Cameron suggested an optional argument to throw an error, which to me is the preferred approach.

As a general FYI, "silent fail" is something that should never, ever, be done. I know a lot of times we say, "Hey, this is a guideline and exceptions exist, blah blah blah", but, no, sorry, I don't agree. Silent Fail is evil. It's the mushy green peas of development. Now - take that statement and argue with me in the comments. ;)


Image credit: http://adashofflavour.blogspot.com/2010/08/mushy-peas.html

p.s. Also note this other cfindex bug Scott Stroz found: https://bugbase.adobe.com/index.cfm?event=bug&id=3785874


(Fri, 11 Jul 2014 10:21:00 -0400)
[view article in new window]

Unexpected behavior with Axis2 web services in ColdFusion

Credit for this find goes to Steve Seaquist. He and I have been discussing this over email the last few days. Ok, quiz time, look at the following CFC:

component  {

	variables.weird = 0;
	remote numeric function testWeird() {
		variables.weird++;
		return variables.weird;
	}

}

Imagine you are calling this CFC directly from JavaScript code. As you know, each time you call a CFC remotely, it will be created on the fly. So calling this CFC's testWeird method N times will always return 1. However, what happens if you call it as a web service?

If you said 1, you would be wrong. Under Axis2, the CFC is now persistent. I have no idea why. But if you make calls to the CFC as a webservice, you will see that the result increments by one every time you do so. I've got no idea what scope this is living in (my first test was early this morning, hours ago), and the only way to clear this is to edit the CFC itself. (Refreshing the WSDL also does it.) If you add wsversion="1" to the component tag it will use the earlier Axis library and act the right way.

Perhaps this is some known "feature" of Axis2. I did a bit of Googling but nothing really struck me as relevant. Whether good or bad, this is one of those things I did not expect (neither did Steve) so I'm blogging to warn others.

Also, don't use web services. Seriously. Unless someone is holding a gun to your head, just use simple JSON services and don't overcomplicate stuff.


(Thu, 10 Jul 2014 10:08:00 -0400)
[view article in new window]

Video example: collection-repeat performance in Ionic

If you follow me on Twitter, you know I've been raving about Ionic the past few weeks. I've played around with it a bit but haven't yet built a proper "sample" app. I still plan on doing so sometime soon. Today though I wanted to share a little experiment I built last night.

One of the directives that ships with Ionic is the ability to build nicely formatted lists. If you create a sample Ionic application based on the Tabs starter application, you can see a nice example of this.

I won't share all the code behind this as you can see it for yourself if you create a new application based on the Tabs app (ionic start somename tabs), but here is the view used to render that screen shot. Note that the data comes from a service (with static values) and everything is set up by an Angular controller.

<ion-view title="Friends">
  <ion-content class="has-header">
    <ion-list>
      <ion-item ng-repeat="friend in friends" type="item-text-wrap" href="#/tab/friend/{{friend.id}}">
        {{friend.name}}
      </ion-item>
    </ion-list>
  </ion-content>
</ion-view>

Nice and simple, right? This is why I'm really digging Angular lately. While this works, one of the issues you run into is performance if your list is large. Of course, "large" is relative. But if you were to go from a list of a few friends to a few thousand, you will see performance degrade due to the size of the DOM being rendered.

Turns out - the devs at Ionic have a solution for it. By making use of collectionRepeat, you get an updated list directive that smartly handles large lists. It can dynamically add to and remove from the DOM based on what is actually visible and has much better performance for larger lists.

It isn't just a simple code change. If you read the docs for it you will note some things you have to take care of versus the simpler list control, but it isn't that difficult. I modified one of the tabs to make use of it like so:

<ion-view title="Account">
  <ion-content class="has-header">
    <ion-list>
      <ion-item collection-repeat="friend in friends"
      			collection-item-width="'100%'"
      			collection-item-height="80">
      			{{friend.name}}
      </ion-item>
    </ion-list>
  </ion-content></ion-view>

I should point out - this is not an exact replacement. As you'll see in a second, the UI is different. I could have fixed that but I was just interested in seeing the actual performance difference. I went into the service file for the app and modified it to add a bit more data:

angular.module('starter.services', [])

/**
 * A simple example service that returns some data.
 */
.factory('Friends', function() {
  // Might use a resource here that returns a JSON array

  // Some fake testing data
  var friends = [
    { id: 0, name: 'Scruff McGruff' },
    { id: 1, name: 'G.I. Joe' },
    { id: 2, name: 'Miss Frizzle' },
    { id: 3, name: 'Ash Ketchum' }
  ];

  //let's blow the shit out of this...
  for(var i=0;i<20000;i++) {
    friends.push({id:i+4, name:"Person "+i});
  }
  
  return {
    all: function() {
      return friends;
    },
    get: function(friendId) {
      // Simple index lookup
      return friends[friendId];
    }
  }
});

Not very realistic, but you get the idea. We've gone from 4 items to 2004. So what was the difference? Instance access to the list instead of about 5 seconds of waiting. See the video below for an example.

That's pretty significant if you ask me. For the heck of it, I tried 200K rows as well and it performed just as well. Speaking to one of the devs last night, the only real issue you have with this control is the amount of memory the actual data is holding, not the rendering.

In case you were wondering if Ionic is "just another UI" library, this is a great example of why it is so much more than that. Definitely check it out!


(Thu, 10 Jul 2014 06:13:00 -0400)
[view article in new window]


© The connection to the CAMDEN's RSS feed has timed out - please try again later. We are sorry for any inconvenience this may have caused.