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.



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 a 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 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]

Cordova Plugins update, and new Contacts demo

Yesterday the Cordova team released updated plugins. You can read the details here: Plugins Release: July 8, 2014. Of particular interest to me was the update to the Contacts plugin, specifically the addition of a new API, pickContact.

Previously, you could search the device's contact database, but there was no way to provide a list of all the contacts so the user could easily select one. Third-party plugins existed to provide that functionality, but now it is provided directly with the core plugin itself via pickContact. Here is a simple example:

document.addEventListener("deviceready", init, false);

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

function doContactPicker() {
	navigator.contacts.pickContact(function(contact){
		console.log('The following contact has been selected:' + JSON.stringify(contact));
		//Build a simple string to display the Contact - would be better in Handlebars
		var s = "";
		s += "<h2>"+getName(contact)+"</h2>";

		if(contact.emails && contact.emails.length) {
			s+= "Email: "+contact.emails[0].value+"<br/>";
		}

		if(contact.phoneNumbers && contact.phoneNumbers.length) {
			s+= "Phone: "+contact.phoneNumbers[0].value+"<br/>";
		}

		if(contact.photos && contact.photos.length) {
			s+= "<p><img src='"+contact.photos[0].value+"'></p>";
		}

		document.querySelector("#selectedContact").innerHTML=s;
	},function(err){
		console.log('Error: ' + err);
	});
}

/*
Handles iOS not returning displayName or returning null/""
*/
function getName(c) {
	var name = c.displayName;
	if(!name || name === "") {
		if(c.name.formatted) return c.name.formatted;
		if(c.name.givenName && c.name.familyName) return c.name.givenName +" "+c.name.familyName;
		return "Nameless";
	}
	return name;
}

The actual API is relatively simple, just navigator.contacts.pickContact. The first argument is a success callback while the second is for errors. This fires the native contact picker UI for the device. For example, here it is in iOS:

For the most part, the Contact API is a bit simple, but you do run into a few quirks. I strongly recommend reading the full docs for the plugin. You can see in my code above where I do a bit of work for iOS to handle how it does names.

Finally, my sample code displays the contact you selected.

I've added this demo to my Cordova-Examples repository on GitHub.


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

Proof of Concept Cordova integration with Brackets

The past few days I've been working on a new Brackets extension that integrates with the Cordova command line tool. I've got a rough draft of it ready with almost every feature prepared so I thought I'd go ahead and let people know in case they wanted to start hacking on it. As it is not complete yet I've not added it to the extension manager, but once I wrap the last feature I'll go ahead and submit it. You can find the code for the extension on GitHub: https://github.com/cfjedimaster/Cordova-Extension.

The extension relies on the Cordova CLI to exist, so keep that in mind, and it assumes you've already set up some mobile SDKs already. This extension doesn't remove the need to go through those steps, it merely serves to help you use those tools from within Brackets. Once loaded, you get a new icon in the right side menu:

It is a bit hard to see, but the icon is grayscale. Switching to a Cordova project switches the state to enabled.

Clicking the icon opens up a bottom panel with two tabs: Platforms and Plugins. Platforms is just that - a list of currently installed platforms for the project that shows which are enabled.

All I'm doing here is wrapping calls to the CLI. To be clear, I don't think the Cordova CLI is hard to use, but I think having a quick visual look at my Cordova settings is pretty useful. Note that there is poor visual feedback currently in the extension. The very first time you use a platform, like Firefox OS, the CLI will fetch the bits remotely. That can take 30-60 seconds. My extension doesn't provide any good visual feedback that it is working. It will correctly update the UI when done, but you don't get a loading indicator. For platforms already installed system-wide though it will be super quick. Also, Run doesn't actually work well now. I emulate more often anyway.

The next tab is Plugins. This will allow you to list, remove, and add plugins. Currently only list works.

The search field will hit plugins.cordova.io and use autocomplete. I may even "fudge" it a bit since "accelerometer" doesn't match the plugin it uses (device-motion). You won't believe how many times that screws me up.

Anyway, I think this plugin could be useful more for the "what am I using" feature than actually firing off builds and modifying platforms. Let me know what you think. I wrote the code somewhat quickly so it has not been linted yet. I plan on doing that before my "official" release for 1.0.


(Tue, 08 Jul 2014 06:18: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.