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.



Datalist version of Country Dropdown

Earlier this morning I saw the following tweet:

I thought it made sense and figured - why not build a version using the <datalist> tag? I began by doing a quick Google search for a select drop down of countries. I came across this one: ISO Country List - HTML select/dropdown snippet. From that I simply copied and pasted the HTML. Here it is - with a few countries cut out. Like most of the world.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Select Country</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
</head>
<body>

<form>

<!-- credit: http://www.freeformatter.com/iso-country-list-html-select.html -->
<select>
	<option value="AF">Afghanistan</option>
	<option value="AX">Åland Islands</option>
	<option value="AL">Albania</option>
	<option value="YE">Yemen</option>
	<option value="ZM">Zambia</option>
	<option value="ZW">Zimbabwe</option>
</select>

</form>

</body>
</html>

And yeah - this works - but the UX is not terribly optimal. At minimum the US should really be on top if your audience is principally American. I mean, this is cool, right?

Ok, so how to convert this to a datalist? I just need to add a text input and convert the dropdowns. The current code uses both a value and a text field, but datalists support only one value. I went into my console and wrote this code:

s = $("select");
html = "";
for(var i=0;i<s.options.length; i++) { html+= "

This gave me a copy of the rendered string in my clipboard that I just then copied to a new file:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Select Country</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
</head>
<body>

<form>

<!-- credit: http://www.freeformatter.com/iso-country-list-html-select.html -->
<input name="country" list="countries">
<datalist id="countries">
<option value="Afghanistan">
<option value="Åland Islands">
<option value="Albania">
<option value="Algeria">
<option value="Zambia">
<option value="Zimbabwe">
</datalist>

</form>

</body>
</html>

The result is a bit nicer I think.

Of course, there are a few disadvantages to this approach. First, you lose the associated country code. But you could easily do that server side. If for some reason a user enters a made up country, or simply mispels, then you would need to either just accept it or make them enter it again. The other issue is what happens if the user is on a browser that doesn't support datalist. The cool thing is that - guess what - they can still type - so nothing is really lost. You could write a few lines of code to detect support for datalist and where it doesn't exist, dynamically replace the tag (you can still get it even if the browser doesn't support it) with a select tag.

On the off chance you want to try this, here it the giant dropdown version and here is the datalist version.


(Wed, 27 Aug 2014 16:03:00 -0400)
[view article in new window]

Chrome 37 has landed - but what exactly changed?

This is something of a pet peeve of mine, and something I tend to whine about from time to time, so I will more than understand if you take this opportunity to stop reading and do something more productive than to listen to me complain. Still here? Good. Yesterday Chrome 37 was released. Awesome. So what changed? Let's (try) to find out.

The first thing I'd expect in any application is an easy way to get to the release notes, probably from the menu: Some App / About Some App. For Chrome, the "Chrome / About Google Chrome", takes you to this page:

Nothing here seems to imply anything informative about the release. The Help link leads to documentation, but nothing specific about 37. (Well, I assume that if 37 added feature X, it is covered here, but as I don't know what X is, I can't confirm that.) At the bottom of the About page (not shown in the screen shot above) is a note about Chrome being based on Chromium. I would not expect that link to provide me anything. As Chrome is "based on" Chromium, I'd expect the release notes for Chrome to be different, much like how Adobe Edge Code is based on Brackets. Ok, complete dead end there. Let's try Google.

The first result for "chrome 37 release notes" takes me here: Stable Channel Update. Woot. Hopefully you know that "Stable Channel" is the same as the main release. That's obvious, right? There are three main bullet points on this article:

  • DirectWrite support on Windows for improved font rendering
  • A number of new apps/extension APIs
  • Lots of under the hood changes for stability and performance

So, I kind of expect to learn about two main areas of changes: End User changes and Developer changes. End User changes would be things like, "You can type cowbell in the URL bar to find Christopher Walken." It is the type thing that impacts everyone. As someone who uses Chrome every day - all day - I think I know it well but I'm sure there are things I may not know about. From what I can tell, only one big change landed here, and it only impacts Windows.

In terms of Developer changes, I'm thinking about things like - support for <dialog>, which according to HTML5Rocks, was in Chrome 37 beta. To me that's a pretty cool change, but I can see it not being important enough to be listed. So that web page lists to all the changes as well. You can find this here: https://chromium.googlesource.com/chromium/src/+log/36.0.1985.0..37.0.2062.0?pretty=full.

This is a formatted list of SVN changes for the release. As an example, here is the first item.

Navigation transitions: Added "addStyleSheetByURL" function to insert stylesheet links. If transition-entering-stylesheet is defined in the response headers for the incoming document, they're parsed out, passed to the TransitionPageHelper in the embedder, and are applied to the page via addStyleSheetByUrl at the appropriate time in the transition. This is the chrome side of the CL, blink side here: https://codereview.chromium.org/285623003/ Design doc: https://docs.google.com/a/chromium.org/document/d/17jg1RRL3RI969cLwbKBIcoGDsPwqaEdBxafGNYGwiY4/edit# Implementation details: https://docs.google.com/a/chromium.org/document/d/1kREPtFJaeLoDKwrfmrYTD7DHCdxX1RzFBga2gNY8lyE/edit#heading=h.bng2kpmyvxq5 BUG=370696 Review URL: https://codereview.chromium.org/309143002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278856 0039d316-1c4b-4281-b951-d872f2087c98

Yep - no idea. I'm assuming this is an internal change of some sort. For fun, I did a "Save As PDF", and apparently there is 19 pages of this. I'll be honest - I stopped reading. I did, however, do a CTRL+F for dialog. As I said, it was in Chrome 37 Beta, so I bet it landed, right? Nope - no result for dialog.

And what about "A number of new apps/extension APIs"? Searching for API returns 14 results - the second one being for Android which is nice and all but I'm trying to find out what changed for desktop Chrome.

But here is where things get weird. I went to the MDN page for Dialog, copied their sample code, and it worked perfectly in Chrome 37. So it did ship, right? But it isn't documented. Well, that's fair. Google could have decided that it wasn't quite ready for prime time yet. Many companies will do that.

But is that the case here? I honestly don't know. I expect HTML5Rocks may soon have an article about 37, but unless you know about the site, then how do you find out? How many of my readers know about HTML5Rocks? I'd also have to imagine Google has enough people on staff to get something written in time for release. (Hell, hire me and I'll do it. ;)

For comparison's sake - let's test Firefox.

Ok, useless. Let's try "Help, Firefox Help":

Boom. Ok, a bit small, and not terribly obvious, but there it is - "What's New". This takes you to this page: https://www.mozilla.org/en-US/firefox/31.0/releasenotes/.

This brings you to a wonderfully designed page with labels by each change:

This is perfect. I'd love to see Chrome start doing this. I hope they start doing this.

p.s. Have you noticed recently that textarea's in Chrome will use a green/grey squiggle for grammar issue? This is different than red squiggle for grammar issues.

I say green/grey because I swear this morning I saw a light green squiggle, but I can't reproduce it now.


(Wed, 27 Aug 2014 06:26:00 -0400)
[view article in new window]

Did you do this?

It isn't uncommon for Amazon to ship items from my wish list and leave off the name of the person who sent it to me (and if you ever sent me something and never got a thank you - that's why) - but this gift was sent to me and it wasn't even on my list. So... whoever you are... thank you! It has joined my collection. One day I'll share photos of my Star Wars collection. It hasn't quite hit Hoarders level yet, but I'm getting there.


(Mon, 25 Aug 2014 11:44:00 -0400)
[view article in new window]

My weekend experiment - PebbleJS

Apparently everyone is expecting the announcement of the iWatch in a few weeks, and wearables are going to be "The Thing" for the next few years. But I've been rocking a Pebble for a while now and I really dig it. It isn't as sexy as some of the newer smart watches, and who knows how it will compare to whatever Apple is doing, but it is affordable (reasonably affordable) and simple. If you are considering picking up a smart watch, definitely check it out.

There has been an SDK for the device since it came out, allowing developers to create watch faces and apps with C. You can read more about that on their developer guide site. Recently though an alternative way to develop for Pebble was released - PebbleJS. As you can imagine, this lets you use JavaScript to create Pebble applications. I thought I'd take it for a spin this weekend.

For the most part, it works pretty easily, especially if you make use of their CloudPebble service. (Why, oh why, must everything be "Cloud" something...) I'm not a big fan of browser-based IDEs, but their editor worked decently enough. The editor has JSHint built in so you get real time syntax and best practices warnings.

Sending the application to your watch is also pretty easy. Remember to enable the developer connection via the mobile app. One thing that tripped me up though was getting CloudPebble to recognize my device. It knew about my original connection via the iPhone app, but it didn't recognize that I had switched to using Android as my primary phone. If you go to the Compilation page of CloudPebble, you can enter the IP address of the phone:

But I wasn't sure how to get my IP. I was about to hit one of those "what is my IP" web pages when I checked the mobile app again. On the Developer panel was my IP:

Another issue I ran into was random transfer errors. This seemed to get worse as I worked on my project, and it may have been related to my app being selected in the Pebble app on my device. Switching to another app, then running my compilation, seemed to help, but I wasn't able to ever figure out exactly what made the process fail. It just slowed me down a bit though.

One cool aspect is that you can view console.log messages directly from the web site. This became more and more useful as I went past the starter app and really began to code something.

For my project I thought I'd build a version of the Death Clock. I figured that kinda made sense on a watch anyway. It is here though that I ran into the biggest problem with PebbleJS - setTimeout and setInterval are discouraged. Now - maybe I'm crazy - but I can't imagine what you could build on Pebble that would actually be useful without having some form of interval based processing. I suppose you could build an app, but any type of watch face or game just wouldn't make sense. There is a good reason for this. From the JavaScript guide:

The JavaScript code is part of the Pebble app and bundled in your pbw file. The phone extracts and installs this code when you install your app on Pebble.
Your JavaScript is executed in a sandbox inside the Pebble mobile app.
Your JavaScript is started by a request from the Pebble app. It will be killed when the user exits your application.
The Pebble mobile app will attempt to keep your app running as long as your Pebble app is running. However, under certain circumstances, the phone may kill the Pebble app and your app. If this occurs, the Pebble mobile app will be automatically restarted.

So - yeah - it is actually being run from the mobile app, not the device. This also has one more huge issue. Because the app runs from the mobile app, any user who is using iOS for Pebble will not be able to download your application until the company resubmits their app to Apple. I guess that's not really their fault, but, honestly, it is enough for me to not recommend using this library. To be clear, I think developing for the Pebble is probably worth your while, but I don't think I can suggest using their JS solution. Maybe for prototyping, but nothing more.

Despite the fact that setInterval was discouraged, I figured, why not go ahead and build it anyway:

You can actually download this right now if you are on Android, and, in up to ten days, iOS as well. Unfortunately, there is no public web interface for the Pebble app library. That is a mistake. Hopefully this will be corrected in the future. Other people have made their own libraries though - basically scraping the same data that the mobile app uses. I used this one - http://pas.cpfx.ca/.

And yes - that looks kinda lame, but that's my fault, not Pebble's. I whipped up the marketing assets in about 2 minutes. In case you're curious, this is what the code looks like. And again, I wrote this rather quickly and this does not demonstrate all the different aspects of what you can do.

/**
 * Welcome to Pebble.js!
 *
 * This is where you write your app.
 */

var UI = require('ui');
var Vector2 = require('vector2');
var Settings = require('settings');

var timeLeft = 2022491029;
var hb;

var wind = new UI.Window();
var textfield = new UI.Text({
    position: new Vector2(0, 50),
    size: new Vector2(144, 30),
    font: 'gothic-24-bold',
    text: 'Time to Live:\n'+String(timeLeft),
    textAlign: 'center'
  });
  wind.add(textfield);

var splash = new UI.Card({
  title: 'Death Clock',
  body: ""
});
splash.show();

function decrCounter() {
  timeLeft--;
  if(timeLeft > 0) {
    textfield.text('Time to Live:\n'+String(timeLeft));
  } else {
    textfield.text("Time is up!");
    clearInterval(hb);
  }
}

setTimeout(function() {
  
  splash.hide();
  wind.show();
  var birthDayStr = localStorage["birthday"];
  if(birthDayStr) {
    console.log("got from local storage");
    var bdParts = birthDayStr.split("/"); 
    var bDate = new Date(bdParts[0], bdParts[1], bdParts[2]);
    console.log('using bdate of '+bDate);
    timeLeft = calculateTimeToDie(bDate);
  }
  hb = setInterval(decrCounter, 1000);
  
}, 2000);

Pebble.addEventListener("showConfiguration", function() {
  console.log("showing configuration");
  Pebble.openURL('http://static.raymondcamden.com/deathclockform.html');
});

Pebble.addEventListener("webviewclosed", function(e) {
  console.log("configuration closed");
  //http://forums.getpebble.com/discussion/15172/pebblejs-cloudpebble-unexpected-token-c-at-object-parse-json-error
  if(e.response !="CANCELLED") {
      var options = JSON.parse(decodeURIComponent(e.response));
      console.log(options.day);
      console.log("Options = " + JSON.stringify(options));
      var dayToSave = options.year + "/" + options.month + "/" + options.day;
      localStorage["birthday"] = dayToSave;
      var bDate = new Date(options.year, options.month, options.day);
      console.log('going to test for '+bDate);
      timeLeft = calculateTimeToDie(bDate);
  }
});

//Given a date, return # of seconds left to live
function calculateTimeToDie(born) {
  var now = new Date();
  var timeAlive = Math.floor((now.getTime() - born.getTime())/1000);
  console.log('time alive is '+timeAlive);
  // 75 years
  var avgTime = 2365200000;
  var timeLeft = avgTime - timeAlive;
  return timeLeft;
}        

Again - I'm not sure I can recommend folks use PebbleJS for their "production" apps, but it was an interesting experiment and kinda cool to see something showing up on my watch so quickly.

One more quick note - I can say that I was impressed by the developer forums at Pebble. Even on a Saturday I got pretty quick answers to my questions.

Edit on August 25: In regards to my note about how PebbleJS required you to wait for an App Store resubmission, it looks like I was wrong. A few hours ago, Jonathan Stark shared this with me:

So... on second thought - maybe my "Cool but not recommended" summary needs to be amended to "Cool and ... maybe!" That's a bit wishy washy still but I'm more open to the possibility of doing more with it now.
(Sun, 24 Aug 2014 12:01:00 -0400)
[view article in new window]

IndexedDB - On the move...

Ok, kind of a lame title, but it's Friday so I get a pass, right? I've presented (and written) on IndexedDB over the past few years, but it isn't something I've really worked with, or demoed, lately. There's been some interesting updates related to the technology though so I thought I'd share some news about IndexedDB. Now may be a great time to take a look at it again.

The first, and probably biggest, piece of news is that iOS 8 will support IndexedDB. Combined with Android 4.4 support, it will soon be (relatively) safe to make use of IDB on mobile. You will probably still need to use WebSQL as a fallback for a while, but at least mobile support is beginning, which is a good thing. A very good thing. You can see full support data over at CanIUse. Here is a screen shot (and for folks reading in the future, hit the link instead for the most up to date figures and tell the robot overlords that I always supported them):

Another update related to support is that Chrome added support for Blob data. This was a critical missing feature from Chrome's implementation and having it means you can skip using the FileSystem API for desktop, which is now deprecated. You can read more about it over at HTML5 Rocks: Blob support for IndexedDB landed on Chrome Dev. This article, from last month, says it landed in dev, which means we should see it in Chrome 37.

So... support is decent and getting better. The flip side to that is actually using IndexedDB. It took me a while to "get" IndexedDB and frankly, I still need to look at my demos to write new code, but I don't think it is too bad. That being said, it isn't the friendliest API in the world. There's some good news on that front as well.

First, over the past week I heard about not one, not two, but three different IndexedDB wrappers. I haven't had a chance to play with these yet, but they look pretty interesting and I think you can find one to match your style:

There are probably more out there - but obviously developers are discovering IndexedDB and deciding they need a simpler interface. Another resource I'd check out is this excellent article on MDN: Breaking the Borders of IndexedDB. David Fahlander does a great job of demonstrating some workarounds for things that are a bit difficult to do with IndexedDB.

As I said, now may be a good time to check it out. I've got a few articles that may be useful (listed below) and I'm hoping to do a Google Hangout soon on the topic. (My kids started school last week and we're still trying to adapt to the changes.)


(Fri, 22 Aug 2014 09:45:00 -0400)
[view article in new window]

Project Parfait moves to Creative Cloud

A few months ago I blogged about the awesome new tool we released, Project Parfait. Project Parfait lets you upload and inspect PSDs directly in the browser. For developers like myself this was especially nice. As powerful as Photoshop is, I don't have a lot of experience with it. I found Project Parfait to be a much easier tool. As a web developer, being able to quickly see color palettes, font faces, and sizes was a huge benefit.

The good news is that as of this morning, Project Parfait has graduated from beta tool to a full release as part of the Creative Cloud. By logging into the Creative Cloud (creative.adobe.com), you get the same features you had with Parfait, even at the free tier of CC.

Note that you can also share these PSDs with people not on the Creative Cloud. In the next few months, this feature is going to pop up in other desktop tools as well.

As a quick tour, here is what you'll see different when selecting a PSD file in your Creative Cloud files tab.

Just hit the Extract link in the header and you'll see the same functionality you had with Parfait:


(Thu, 21 Aug 2014 13:49:00 -0400)
[view article in new window]

Follow up to ColdFusion Axis2 Web Service Issue

Last month I blogged about an odd issue with Axis2 web services. Basically, the CFC was persisting past the initial hit. Normally CFCs are recreated on every request. This didn't seem to be the case with Axis2 web services.

I filed a bug report for the issue and today I got a response from an engineer:

This is a change in behavior in how we designed the Axis 2 web services support.

In Axis 1, For every web service request we created a new CFC instance and the request was handled by this new instance. That is why the variables always gets re initialized for every request.

In Axis 2, we are creating the CFC instance the first time only. For every subsequent requests the same instance is used. So you are seeing the caching there. A new CFC instance will be created only if service CFC is modified.

Normally if you are deploying an axis2 based aar, in tomcat, you will see the same behavior.

This was a design decision we took. Are you facing any issues because of this caching? Do you have a requirement to use different CFC instances for each web service request?

So, long story short, this is expected. I want to be clear so people understand the issue. This is not like trusted cache, where changing the code doesn't reflect on the server. This is not like the case where you change a method and have to tell CF to refresh the WSDL. No, this is data persistence, as my example demonstrates. In every other case of using a CFC - either via Ajax or WSDL - this is not how CFCs act.

So... um... yeah. I guess watch out for it. Or take the advice I gave on the last blog entry and avoid WSDL like the plague on humanity it is. If you feel like this was a bad design by the ColdFusion engineers, please post your thoughts on the bug entry, not here. (Or both - just keep in mind I've got no power to change things there. ;)


(Thu, 21 Aug 2014 09:09:00 -0400)
[view article in new window]

Ionic releases an easier path to Cordova / Android development

I promise - I'm not turning into a complete Ionic fan boy, but if you want to call me an unofficial evangelist for them (especially since I don't do that for Adobe anymore), I certainly won't mind. This weekend I was working on the book I'm writing covering Cordova, and as part of that process I worked through the complete setup for doing Cordova development with Android. Most of the time I use iOS but for the book I wanted a platform anyone could use. In fact, I specifically did my testing in Windows since I know that many people use Windows and Cordova development has been a bit tricky there in the past.

It had been a long time since I went through the complete process. What I discovered is that while it is relatively straightforward, there are more steps than I remembered and you really need to pay attention or it is easy to screw up one of the prerequisites. I think my chapter does a great job of discussing this process, but as I said, it was surprising just how much you have to do to get up and running.

So I find this new post by the Ionic team to be very timely: Faster Hybrid Dev with Ionic Vagrant. I had some exposure to Vagrant at CFObjective a few weeks back but I had not actually used it for anything. Vagrant allows you to build development environments - think operating systems with applications and configurations - via a scriptable interface. What this means is - in an ideal world - a person can download a Vagrant image - set it up - and have everything ready to go. I've been through enough projects to know that something like this could be a huge timesaver.

To help with Android/Cordova development, the Ionic folks have released an Ionic Vagrant image. What this means is - after installing Vagrant - you can download their zip, unzip, and type vagrant up. And that's it. The first time you run this be prepared for a long wait. For me it took about 20-25 minutes, and during part of the process I saw some red error messages, but it eventually wrapped up at the end - I had a complete operating system.

With Android.

With Cordova.

And with Ionic.

Hell, screw Ionic (sorry guys!), even if you don't give a rat's mustache about Ionic, this is a huge timesaver in terms of getting up and running with Cordova. If you do decide to check it out, here are a few things to watch out for.

Number one - the commands to check for a connected device require sudo. This is covered on the GitHub and from what I can tell, you will run into this issue. Just do exactly as described:

sudo /home/vagrant/android-sdk-linux/platform-tools/adb kill-server sudo /home/vagrant/android-sdk-linux/platform-tools/adb start-server

And follow up with sudo /home/vagrant/android-sdk-linux/platform-tools/adb devices to ensure your device shows up.

Number two - how do you edit files? For my first test I used ed, but I wouldn't recommend that. (Old school MUD coder here - I used to be a wiz with ed.) Vagrant supports the concept of synced folders, which means you can edit using your preferred browser tools while the files are available to the Vagrant image as well. Thanks to Max Lynch for helping me out with this as I found the docs to be a bit weird. Here is how it works in a nutshell.

If you expanded the image zip to /Users/ray/Downloads/ionic-cordova-android-vagrant-0.1.1, then that folder is available on the image as /vagrant. So while SSHed into the image, I went to /vagrant and made a new Ionic project called android2. It showed up in Finder right way.

I opened this up in Sublime, edited, and went back in Terminal, and sent the project to my Android device. It felt completely the same as normal development. (To be clear, when I SSHed into the image, I did it via Terminal, and you end up ... in Terminal, so this is probably assumed, but I wanted to be clear this wasn't like a VMWare visual type thing.)


(Tue, 19 Aug 2014 16:43:00 -0400)
[view article in new window]

PhoneGap/Cordova Example - Getting File Metadata (and an update to the FAQ)

I decided to move my PhoneGap/Cordova FileSystem FAQ from a Google Doc to my Cordova Examples repository. I figured this would make it a bit easier for folks to edit and simpler for me to commit those changes. You can find the FAQ here:

PhoneGap Cordova File System FAQ

In doing so I decided to quickly knock out one of the questions - how to get metadata about files. This is rather trivial, so my demo app is rather trivial, but hopefully it will help folks. The basic gist is - once you have a FileEntry object, you can then fetch the File object itself (MDN Docs) and fetch various properties. Here is a super simple demo roughly based on the ones I showed earlier. It uses window.resolveLocalFileSystemURL to get index.html.

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);

}

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

function gotFile(fileEntry) {

	fileEntry.file(function(file) {
		var s = "";
		s += "<b>name:</b> " + file.name + "<br/>";
		s += "<b>localURL:</b> " + file.localURL + "<br/>";
		s += "<b>type:</b> " + file.type + "<br/>";
		s += "<b>lastModifiedDate:</b> " + (new Date(file.lastModifiedDate)) + "<br/>";
		s += "<b>size:</b> " + file.size + "<br/>";
		
		document.querySelector("#status").innerHTML = s;
		console.dir(file);
	});
}

Here is a sample of it running in iOS. (Be aware - there is a bug with Android that may prevent this from working. It is reported at the Cordova site.)

So - yeah - not rocket science - but hopefully helpful. This "application" can be grabbed from my Cordova repo: https://github.com/cfjedimaster/Cordova-Examples/tree/master/getfiledata.


(Mon, 18 Aug 2014 16:58:00 -0400)
[view article in new window]

Ionic and Cordova's DeviceReady - My Solution

Folks know that I've been madly in love with the Ionic framework lately, but I've run into an issue that I'm having difficulty with. I thought I'd blog about the problem and demonstrate a solution that worked for me. To be clear, I think my solution is probably wrong. It works, but it just doesn't feel right. I'm specifically sharing this blog entry as a way to start the discussion and get some feedback. On the slim chance that what I'm showing is the best solution... um... yes... I planned that. I'm brilliant.

The Problem

So let's begin by discussing the problem. Given a typical Ionic app, your Angular code will have a .run method that listens for the ionicPlatform's ready event. Here is an example from the "base" starter app (https://github.com/driftyco/ionic-app-base/blob/master/www/js/app.js):

// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
angular.module('starter', ['ionic'])

.run(function($ionicPlatform) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if(window.StatusBar) {
      // Set the statusbar to use the default style, tweak this to
      // remove the status bar on iOS or change it to use white instead of dark colors.
      StatusBar.styleDefault();
    }
  });
})

The ionicPlatform.ready event is called when Cordova's deviceready event fires. When run on a desktop, it is fired when on window.load. Ok, so in my mind, this is where I'd put code that's normally in a document.ready block. So far so good.

Now let's imagine you want to use a plugin, perhaps the Device plugin. Imagine you want to simply copy a value to $scope so you can display it in a view. If that controller/view is the first view in your application, you end up with a race condition. Angular is going to display your view and fire off ionicPlatform.ready asynchronously. That isn't a bug of course, but it raises the question. If you want to make use of Cordova plugin features, and your application depends on it immediately, how do you handle that easily?

One way would be to remove ng-app from the DOM and bootstrap Angular yourself. I've done that... once before and I see how it makes sense. But I didn't want to use that solution this time as I wanted to keep using ionicPlatform.ready. I assumed (and I could be wrong!) that I couldn't keep that and remove the ng-app bootstraping.

So what I did was to add an intermediate view to my application. A simple landing page. I modified the stateProvider to add a new state and then made it the default. In my ionicPlatform.ready, I use the location service to do a move to the previously default state.

.run(function($ionicPlatform,$location,$rootScope) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if(window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }

	  $location.path('/tab/dash');
	  $rootScope.$apply();
  });
})

This seemed to do the trick. My controller code that's run for the views after this was able to use Cordova plugins just fine. How about a real example?

The Demo

One of the more recent features to land in Ionic is striped-style tabs. This is an Android-style tab UI and it will be applied automatically to apps running on Android. The difference is a bit subtle when the tabs are on the bottom:

But when moved to the top using tabs-top, it is a bit more dramatic.

Ok... cool. But I wondered - how can I get tabs on top just for Android? While I'm not one of those people who believe that UI elements have to be in a certain position on iOS versus Android, I was curious as to how I'd handle this programmatically.

Knowing that it was trivial to check the Device plugin, and having a way now to delay the view until my plugins were loaded, I decided to use the approach described above to ensure I could access the platform before that particular view loaded.

Here is the app.js file I used, modified from the tabs starter template.

// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services'])

.run(function($ionicPlatform,$location,$rootScope) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if(window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }

	  $location.path('/tab/dash');
	  $rootScope.$apply();
  });
})

.config(function($stateProvider, $urlRouterProvider) {

  // Ionic uses AngularUI Router which uses the concept of states
  // Learn more here: https://github.com/angular-ui/ui-router
  // Set up the various states which the app can be in.
  // Each state's controller can be found in controllers.js
  $stateProvider

    // setup an abstract state for the tabs directive
  	.state('home', {
		url:"/home",
		templateUrl:'templates/loading.html',
		controller:'HomeCtrl'
	})
    .state('tab', {
      url: "/tab",
      abstract: true,
      templateUrl: function() {
		  if(window.device.platform.toLowerCase().indexOf("android") >= 0) {
			  return "templates/tabs_android.html";			  
		  } else {
			  return "templates/tabs.html";
		  }
	  },
    })

    // Each tab has its own nav history stack:

    .state('tab.dash', {
      url: '/dash',
      views: {
        'tab-dash': {
          templateUrl: 'templates/tab-dash.html',
          controller: 'DashCtrl'
        }
      }
    })

    .state('tab.friends', {
      url: '/friends',
      views: {
        'tab-friends': {
          templateUrl: 'templates/tab-friends.html',
          controller: 'FriendsCtrl'
        }
      }
    })
    .state('tab.friend-detail', {
      url: '/friend/:friendId',
      views: {
        'tab-friends': {
          templateUrl: 'templates/friend-detail.html',
          controller: 'FriendDetailCtrl'
        }
      }
    })

    .state('tab.account', {
      url: '/account',
      views: {
        'tab-account': {
          templateUrl: 'templates/tab-account.html',
          controller: 'AccountCtrl'
        }
      }
    });

  // if none of the above states are matched, use this as the fallback
  $urlRouterProvider.otherwise('/home');

});

You can see where I use the location.path mechanism after the ionicPlatform.ready event has fired. You can also see where I sniff the device platform to determine which template to run. tabs_android.html is the exact same as tabs.html - but with the tabs-top class applied (*). The biggest drawback here is that the application would error when run on the desktop. That could be avoided by sniffing for the lack of window.device and just setting it to some default: window.device = {platform : "ios"};

So that's it. What do you think? I have to imagine there is a nicer way of handling this. Maybe I'm being lazy but I want to use Ionic's killer directives and UX stuff along with Cordova plugins and not have to use an awkward workaround like this.

* A quick footnote. I noticed that if I tried to add tabs-top to the ion-tabs directive, it never worked. For example, this is what I tried first: <ion-tabs ng-class="{'tabs-top':settings.isAndroid}"> I used code in my controller that always set it to true (I wasn't worried about the device plugin yet) and it never actually updated the view. It's like the controller scope couldn't modify the view for some odd reason.
(Sat, 16 Aug 2014 11:25:00 -0400)
[view article in new window]

Eventbrite API Demos

A few days ago a client of mine, Rich Swier of HuB, asked if I could build him two quick demos that made use of the Eventbrite API. I whipped up the two demos for him and once done, he graciously allowed me to share it on my blog. (Thank you Rich!) I will warn you that this code was written for ColdFusion 8 so it is entirely tag based. At the very end a mod was made for ColdFusion 9. Obviously it could be converted to script and perhaps improved in other ways as well, but I hope this is useful for folks who want to play with the Eventbrite API in the future.

Before making use of these demos yourself, you will want to get yourself an authorization token for your account. After going to developer.eventbrite.com, click Get Started. Note the green button on the right.

When I tested this a few days ago, it worked fine for me, but failed for Rich as he wasn't the primary account owner for his organization. Unfortunately the "failure" was a blank white screen. I got a response from Eventbrite on their forums (https://groups.google.com/forum/#!forum/eventbrite-api) about this within a day, but I'm not sure if it is fixed yet. If you do get a "white screen of death" error though check to see if there are other people in your group using Eventbrite.

Once logged in, add a new app, and when done, make note of the OAuth token. My API makes use of this to make authenticated calls for your organization. Finding the ID of your organization can be a bit weird too. Unfortunately I can't find the tip that helped me with this before (I believe it was an Eventbrite staff member), but in order to get your organization ID, login, go to your profile page, and find your organizer page URL:

The organizer ID is the numeric part at the end of your URL. I've highlighted it in the screen shot above. It is a bit silly that the value isn't called out specifically either on the developer page or on your profile, but, there it is.

Ok, so finally, with your token and organizer ID, you can perform some basic searches against the API. For Rich, I only needed Search, but their API supports pretty much everything imaginable as far as I can tell. I created a simple CFC that is initialized with the auth token. First, the Application.cfc used to set up the CFC.

<cfcomponent output="false">
	
	<cfset this.name = "RichSwierEventBrite">
	
	<cffunction name="onApplicationStart" access="public" returntype="boolean" output="false">
	
		<cfset application.eventBrite = {}>
		<cfset application.eventBrite.token = "secret">
		<cfset application.eventBrite.orgid = "3983270067">
		
		<cfset application.eventBriteApi = createObject("component", "eventbrite").init(application.eventBrite.token)>
		
		<cfreturn true>
	</cffunction>
	
	<cffunction name="onRequestStart" access="public" returntype="boolean" output="false">

		<!--- TODO: remove 1 --->
		<cfif structKeyExists(url, "init") or 1>
			<cfset onApplicationStart()>	
		</cfif>
		
		<cfreturn true>
	</cffunction>
	
</cfcomponent>

And here is the CFC itself. Again, this was written for ColdFusion 8, so pardon all the darn tags. I could have used script I suppose, but I ran into some issues with even script-based UDFs on his server so I went the safe route. Also note I ran into cfhttp issues with the https server Eventbrite used. This is why I used a workaround described a few years ago on my blog. On ColdFusion 10 I didn't have the issue at all.

<cfcomponent output="false">

	<cffunction name="init" access="public" returnType="eventbrite" output="false">
		<cfargument name="token" type="string" required="true">
		<cfset variables.token = arguments.token>		
		<cfreturn this>
	</cffunction>
	
	<cffunction name="getEvents" access="public" returnType="struct" output="false">
		<cfargument name="organizationid" type="string" required="true">
		<cfargument name="startdate" type="date" required="false">
		<cfargument name="enddate" type="date" required="false">

		<cfset var result = "">
		<cfset var content = "">
		<cfset var apiurl = "https://www.eventbriteapi.com/v3/events/search?organizer.id=#arguments.organizationid#">
		<cfset var startUTC = "">
		<cfset var endUTC = "">
		<cfset var objSecurity = "">
		<cfset var storeProvider = "">
		
		<cfif structKeyexists(arguments, "startdate")>
			<cfset startUTC = getIsoTimeString(arguments.startdate, true)>
			<cfset apiurl &= "&start_date.range_start=#startUTC#">
		</cfif>
		<cfif structKeyexists(arguments, "enddate")>
			<cfset endUTC = getIsoTimeString(arguments.enddate, true)>
			<cfset apiurl &= "&start_date.range_end=#endUTC#">
		</cfif>
		
		<cfset apiurl &= "&token=#variables.token#">

		<cfset objSecurity = createObject("java", "java.security.Security") />
		<cfset storeProvider = objSecurity.getProvider("JsafeJCE") />
		<cfset objSecurity.removeProvider("JsafeJCE") />
		
		<cfhttp url="#apiurl#" result="result">

		<cfset objSecurity.insertProviderAt(storeProvider, 1) />
		
		<cfset content = deserializeJSON(result.filecontent)>
		<cfreturn content>

	</cffunction>
	
	<cfscript> 
	// I take the given date/time object and return the string that
	// reprsents the date/time using the ISO 8601 format standard.
	// The returned value is always in the context of UTC and therefore
	// uses the special UTC designator ("Z"). The function will
	// implicitly convert your date/time object to UTC (as part of
	// the formatting) unless you explicitly ask it not to.
	function getIsoTimeString(datetime) {
	 
	 	if(!structKeyExists(arguments, "convertToUTC")) {
	 		convertToUTC = true;	
	 	}
	 	
	    if ( convertToUTC ) {
	        datetime = dateConvert( "local2utc", datetime );
	    }
	 
		// When formatting the time, make sure to use "HH" so that the
		// time is formatted using 24-hour time.
		return(dateFormat( datetime, "yyyy-mm-dd" ) &
		    "T" &
	    	timeFormat( datetime, "HH:mm:ss" ) &
		    "Z"
	        );
	 
	}
	</cfscript>
	
</cfcomponent>

Nothing special really. I just construct the URL and fire off the call. That UDF at the end may be found on CFLib: getIsoTimeString. As I said, I built a grand total of one function for the API because that's all he needed. Now let's look at the actual demos. The first demo makes use of Google Maps. I perform a search for future events, get their locations, and display them on a map.

For the most part, this was simple, but the issue I ran into (and this is a reported issue with the API), is the location information for the venue wasn't including the full address. There is a venue API that returns the proper information, but that would mean adding N more calls over http to fetch the data. I simply warned Rich that the address won't be working properly until they fix the API. To be clear, I'm talking about the textual part of the address. The longitude and latitude work just fine. Right now when you click you get some detail and the ability to click for the Eventbrite URL.

Now let's look at the code that generates the map.

<!--- only events today and onwards --->
<cfset dtNow = now()>

<cfif structKeyExists(url, "clearcache")>
	<cfset cacheRemove("eventbrite.futureEvents")>
</cfif>

<cfset events = cacheGet("eventbrite.futureEvents")>
<cfif isNull(events)>
	<cfset eventData = application.eventBriteApi.getEvents(application.eventBrite.orgid,dtNow)>

	<cfset cachePut("eventbrite.futureEvents", eventData.events,0.5)>
	<cfset events = eventData.events>
</cfif>

<!DOCTYPE html>
<html>

<head>
<style type="text/css">
#map_canvas { width: 500px; height: 500px; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
function initialize() {
	
    var latlng = new google.maps.LatLng(41.5, -98);
    var myOptions = {
        zoom: 3,
        center: latlng,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    };
    var map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);

	<cfloop index="x" from="1" to="#arrayLen(events)#">
		<cfset event = events[x]>
		<!--- conditionally format address --->
		<cfset address = "">
		<cfif structKeyExists(event.venue, "name") and event.venue.name is not "undefined">
			<cfset address &= event.venue.name>
		</cfif>
		<cfif structKeyExists(event.venue, "address_1") and event.venue.address_1 is not "undefined">
			<cfset address &= "<br/>" & event.venue.address_1>
		</cfif>
		<cfif structKeyExists(event.venue, "address_2") and event.venue.address_2 is not "undefined">
			<cfset address &= "<br/>" & event.venue.address_2>
		</cfif>
		<cfif structKeyExists(event.venue, "city") and event.venue.city is not "undefined">
			<cfset address &= "<br/>" & event.venue.city>
		</cfif>
		<cfif structKeyExists(event.venue, "state") and event.venue.state is not "undefined">
			<cfset address &= ", " & event.venue.state>
		</cfif>
		<!--- format times --->
		<cfset startDate = listFirst(event.start.local, "T")>
		<cfset startDate = dateFormat(startDate, "mm/dd/yy")>
		<cfset startTime = listLast(event.start.local, "T")>
		<!--- strips off seconds, which is silly --->
		<cfset startTime = mid(startTime, 1, len(startTime)-3)>
		<cfset startTime = timeFormat(startTime, "h:mm tt")>
		
		<cfset endDate = listFirst(event.end.local, "T")>
		<cfset endTime = listLast(event.end.local, "T")>
		<!--- strips off seconds, which is silly --->
		<cfset endTime = mid(endTime, 1, len(endTime)-3)>
		<cfset endTime = timeFormat(endTime, "h:mm tt")>
		
		<!--- currently assumes same date --->
		<cfset dateStr = startDate & " at " & startTime & " to " & endTime>
		<cfoutput>
		var pos = new google.maps.LatLng(#event.venue.latitude#,#event.venue.longitude#);
	 	var marker#x# = new google.maps.Marker({
	    	map: map, 
	        position: pos,
	        title: "#jsStringFormat(event.name.text)#"
	    });

		var infowindow#x# = new google.maps.InfoWindow({
			content: '<b>#jsStringFormat(event.name.text)#</b><p/>#jsStringFormat(address)#<br/>#jsStringFormat(dateStr)#<br/><a href="#event.url#">Details</a>',
			maxWidth: 250
		});
		
		google.maps.event.addListener(marker#x#, 'click', function() {
			infowindow#x#.open(map,marker#x#);
		});
  	    </cfoutput>
	</cfloop>
}
</script>
</head>

<body onload="initialize()">

<div id="map_canvas"></div>

</body>
</html>

Again - pretty simple. Get the events and then just iterate over them to create map markers using the Google API. I don't like the ColdFusion code inside the JavaScript block there and I'd probably consider moving that outside. That way when I create the markers the code would be simpler. (And obviously, if this were a production application I'd use something like FW/1 and do most of that work in the controller/service layer anyway.)

So that's maps. The next demo he requested was a calendar. I decided to use the FullCalendar jQuery plugin as it worked well for me before. (See my blog post from a few years back.) In order to work with the client-side code, I wrote a quick CFC to handle calls to my application scoped Eventbrite API. It also handles shaping the data so it makes the FullCalendar happy. I could have added some RAM-based caching here, but I found that the API was fast enough for month based searches where it didn't feel necessary. Then again - I'd probably consider adding it anyway just to ensure you don't hit any rate limits.

<!---
Main wrapper to calls to the EB api. Handles the caching so EB can be a bit simpler.
Also handles converting EB events for FullCalendar
--->
<cfcomponent output="false">
	
	<cffunction name="getEvents" access="remote" output="false" returnformat="json">
		<cfargument name="start" type="string" required="true">
		<cfargument name="end" type="string" required="true">
		
		<cfset var eventData = application.eventBriteApi.getEvents(application.eventbrite.orgid,parseDateTime(arguments.start), parseDateTime(arguments.end))>
		<cfset var result = []>
		<cfset var x = "">
		<cfset var event = "">
		
		<cfloop index="x" from="1" to="#arrayLen(eventData.events)#">
			<cfset event = {}>
			<cfset event["title"] = eventData.events[x].name.text>
			<cfset event["start"] = eventData.events[x].start.local>
			<cfset event["end"] = eventData.events[x].end.local>
			<cfset event["url"] = eventData.events[x].url>
			<cfset arrayAppend(result, event)>	
		</cfloop>
		
		<cfreturn result>
	</cffunction>
	
</cfcomponent>

Finally, here is the front end using FullCalendar. It is a page with just the calendar and a DOM element used to handle the loading of remote data. I could have designed that a bit better but I assumed the client would have their own ideas about that.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width">
		<link rel='stylesheet' href='fullcalendar/fullcalendar.css' />
		<script src='fullcalendar/lib/jquery.min.js'></script>
		<script src='fullcalendar/lib/moment.min.js'></script>
		<script src='fullcalendar/fullcalendar.min.js'></script>
		<script>
		$(document).ready(function() {

		    $('#calendar').fullCalendar({
		    	eventSources: [
		    		{
			    		url:'api.cfc?method=getevents'
			    	}
		    	],
		    	loading:function(isLoading, view) {
		    		if(isLoading) {
			    		$("#loading").html("<i>Loading data...</i>");
			    	} else {
			    		$("#loading").html("");
			    	}
		    	}
		    });

		});		
		</script>

    </head>
    <body>
    	
		<div id="loading"></div>
		<div id='calendar'></div>

    </body>
</html>

I have attached the code as a zip to this blog entry. Note that the token and organization ID are not in the Application.cfc file so you will need to set them yourself.


(Fri, 15 Aug 2014 05:50:00 -0400)
[view article in new window]

Review: Code School's JavaScript Best Practices

Yesterday evening I finally wrapped up Code School's JavaScript Best Practices course and I thought my readers might like to know what I think. In general, this is a great course marred by poor challenges, surprisingly poor challenges, but still worth your time.

First and foremost, the content of this course is incredible. For a while now I've been thinking about what it means for a developer to improve their JavaScript skills. To go from a "user", someone who knows basic syntax and can copy and paste code to a "developer", someone who is comfortable with the language and understands performance and organizational principles. I'm not talking about Ninja level skills here. Just good, intermediate level skill levels. I think there is a dearth of material aimed at developers who want to achieve this level and it is something I'm personally trying to help with as well. With that context in mind, this course is exactly what I think developers should be taking. Let's look at the material.

Section one, "The Sword of Syntax", covers ternary conditionals, logical assignments (OR and AND), and Switch blocks. In general, this was the section that was probably least useful to me, but I still found the material useful. Just the presentation of the topics themselves were a bit different and I felt like I got something from it. I do think this section will be the one most folks will skip over, but I recommend going through it anyway. Don't forget you can watch the videos at 1.5X speed to move things along a bit quicker.

Section two is "The Pendant of Performance". Topics cover loop optimization, script execution, general performance tips, and measuring. Performance topics can be very confusing, and sometimes a bit difficult to relate to your own development, but the course does a great job at keeping this material practical, which is an important thing for me.

Section three is "The Crystal of Caution". This one covers comparisons, error handling, and some oddities of the language itself. This section in particular serves as a great warning to the types of mistakes developers may make in their code and not realize until later.

The final section, "The Mail of Modularity", discusses namespacing and modularity. It does a good job of introducing the basics of encapsulation in JavaScript. If you are currently writing JavaScript and discovering that you need help with organization, this will be a great lesson for you.

The speaker, Jason Millhouse, is great. He has a good pace, a clear voice, and a sense of humor. I think he may be my favorite speaker at Code School so far and I'm hoping he has done other courses that I can take.

So, that's the good. What's the bad? In general, I've been very impressed with how Code School handles challenges for its material. I find the questions targeted well, interesting, and just enough of a challenge to motivate you to pay attention well and think before you answer. My experience with the challenges with this course though were very poor. It got to the point where I was dreading the challenges and even considering just skipping them. Let me give you an example of one of the first questions:

Given foo is true, what is the result of name = foo ? "ray" : "beer".

If you had said, ray, you would have been wrong. Nope, the answer is "ray". And... ok... I get it. Including the quotes is a bit more precise, but given the nature of the challenge (type your answer in this box), it seems overly specific and unnecessary. And yes, that's a minor quibble, but it was only the beginning.

Next, I ran into a question where the answer didn't make sense. It discussed an aspect of performance that was not covered in the video. While it was possible I had missed it, I discovered other people had the same issue as well. There is a post on their forums, but what concerns me is that there is no official response yet. Remember, Code School is a paid course, so I'd expect some kind of response on this thread.

How about another example? One of the things Code School does well in their code challenges is to provide an editor where you can only edit in certain parts. So they may show a function and ask you to fix one part of it. Only that part will be editable. It is a nice way to focus your efforts and ensure you don't spend too much time writing code that isn't relevant to the challenge. In general - I dig this. But then I encountered the challenge where I was asked to move a script tag from the head block to right before the body. There were - literally - two places in the file you could edit. So the challenge was - move the line of code to the other place - the only other place. I guess an "easy win" is nice every now and then but that was just ridiculous.

Speaking of ridiculous, towards the end of the course there were a few questions that looked like this:

My ____ is ____ and I ____ about ____ a day when I ___ to the ____ and do ____ after I ____ so and _____. Then I ____ while ____ the ____.

After this were four answers that looked like this: Moo, Zoo, Something, AnotherWord, More, Less, Foo, Etc, OMG, Etc. So the intent is - take each word from the list and see if it makes sense in the paragraph. Speaking just for myself - that made my brain explode and it felt like a horrible challenge. After trying one of these, I simply skipped the rest of them.

I had more examples, but honestly, I can't remember them. Most likely I got so frustrated towards the end that I was probably getting annoyed by things I wouldn't have normally. It was a big disappointment.

However...

Despite all of this, I cannot stress enough how much I enjoyed the video content, and how much I felt that this was exactly the course that a lot of people need. Honestly, screw what I said about the challenges. I really hope Code School takes a look at them, but I wouldn't hesitate at all to pay for the course. I mean, we're talking 20 bucks or so for about 4 hours of material (and that's just one course), so this is a no-brainer.


(Wed, 13 Aug 2014 06:08:00 -0400)
[view article in new window]

Presentation tonight on Apache Cordova

I was originally going to be presenting just to the Memphis Technology User Groups tonight, but they have graciously said they don't mind if I open this up to the public. If you want to hear me give an introduction to Cordova, join me online tonight at 6:30 CST here: https://my.adobeconnect.com/raysworld/. I will be giving priority for QA to the Memphis group of course, but all our welcome to join in and - hopefully - learn something as well. This will be over Adobe Connect so be sure to use a Flash-enabled browser.


(Tue, 12 Aug 2014 09:07:00 -0400)
[view article in new window]

Centering a Google Map on America

Earlier this morning I was building a Google Map demo for a client (using EventBrite data - I'll share that if I can) and I needed to center a Google Map on America. There are a couple ways of doing this and I thought I'd share them along with some screen shots so you can see the results.

The first Google result I found led to this Stack Overflow result: How to center Google Map on a country by name. The answer uses Geocoding to translate a simple country name, like America, into the right results. You'll need to scroll down a bit to see a version of the code for the current version of Google's API, or just check out the full sample below.

<!DOCTYPE html>
<html>

<head>
<title>Demo One</title>
<style type="text/css">
#map_canvas { width: 500px; height: 500px; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
function initialize() {

	var country = "United States"

    var myOptions = {
        zoom: 3,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    };
	
    var map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);

	var geocoder = new google.maps.Geocoder();
	geocoder.geocode( { 'address': country }, function(results, status) {
		if (status == google.maps.GeocoderStatus.OK) {
			map.setCenter(results[0].geometry.location);
		} else {
			alert("Could not find location: " + location);
		}
	});

}
</script>
</head>

<body onload="initialize()">

<div id="map_canvas"></div>

</body>
</html>

This gives you:

Looks good to me - but the geocoding bugs me. For every single visitor to your site, a request will be made to Google to ask it where America is. Most likely, America is not going to move. Probably. And while this request is pretty darn fast, there's no real reason for you to geocode this constantly. I did another quick Google and discovered this Wikipedia page: Geographic center of the contiguous United States. It defined the longitude and latitude for America as 39°50?N 98°35?W. I rewrote my code to simply use these hard coded values.

<!DOCTYPE html>
<html>

<head>
<title>Demo Two</title>
<style type="text/css">
#map_canvas { width: 500px; height: 500px; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
function initialize() {

    var latlng = new google.maps.LatLng(39.5, -98.35);
    var myOptions = {
        zoom: 3,
        center: latlng,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    };

	var map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);

}
</script>
</head>

<body onload="initialize()">

<div id="map_canvas"></div>

</body>
</html>

And the result:

Looks a tiny bit different to me. So I went back to the first demo and added this line: console.log(results[0].geometry.location.lat(), results[0].geometry.location.lng());. I checked the console for the values and simply updated the numbers.

I zipped up the demos (including a third demo with the values returned via the console) and included it as a zip.


(Sat, 09 Aug 2014 11:22:00 -0400)
[view article in new window]

Avoid Zero (or super short) Application Timeouts

So here's a doozy for you. Over the past week or so I exchanged emails with a reader who was having an odd issue with ColdFusion mappings. Specifically the code he wrote to actually use the mappings would fail to work if he used application specific mappings. Switching to mappings defined on the server fixed it.

After I failed to notice it (and you can guess what's coming up based on the title of the entry), user BKBK on the Adobe forums pointed out that he had a 0-length application timeout: this.applicationTimeout = createTimeSpan(0,0,0,0);. Basically the application, and its settings, including the application mappings, timed out before his code could use it.

Why did he have it in the first place? He wanted a simple way to reload the Application on every request. For that I'd simply use a hook in your onRequestStart to run onApplicationStart():

function onRequestStart(string req) {
  onApplicationStart();
}

To be clear, this is not really reloading the application. It is just manually running your onApplicationStart method which is - 99% of the time - probably want you want. (During testing anyway, not in production.) You can also wrap that with a simple if(structKeyExists(url, "init")) block as well.


(Fri, 08 Aug 2014 15:14: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.