Sunday, December 13, 2009

Converting Google Picasa's embedded slideshow code to valid XHTML/HTML

Defining the Problem:

You have created a picture album in Google Picasa and want to embed a slideshow of that album in your website or blog. Fortunately, Google Picasa makes it easy to generate the code to do so. All you have to do is copy and paste the generated code into the HTML of your site.

Here's an example of that generated code, using my Chinese Zodiac desktop wallpaper album:

<embed type="application/x-shockwave-flash" src="http://picasaweb.google.com/s/c/bin/slideshow.swf" width="144" height="96" flashvars="host=picasaweb.google.com&hl=en_US
&feat=flashalbum&RGB=0x000000&feed=http%3A%2F%2Fpicasaweb.google.com%2Fdata%2Ffeed%2Fapi%2Fuser%2FJonah.Chanticleer%2Falbumid%2F5220445689687444257%3Falt%3Drss%26kind%3Dphoto%26hl%3Den_US" pluginspage="http://www.macromedia.com/go/getflashplayer">

</embed>

Unfortunately, Picasa's generated code uses the <embed> tag, which is not part of the HTML/XHTML standards (see http://validator.w3.org/docs/help.html#faq-flash for more information). Furthermore, the value assigned to the flashvars attribute of the <embed> tag has multiple ampersands in it, and those ampersands are not properly URL-encoded (see http://www.htmlhelp.com/tools/validator/problems.html#amp for details). This means the code will break validation for any page into which you paste it. Many people don't care if their pages fail to validate, but since you've made the effort to find this article and read this far, I'm assuming you are a web development professional who does care if your page throws a web-browser into "quirks mode."(http://en.wikipedia.org/wiki/Quirks_mode)

Creating a Solution:

To regain proper validation, we need to address the two following issues:

1. Switch from <embed> to <object>, which is part of the HTML/XHTML standards

2. Properly encode the ampersands by changing them from "&" to "&amp;"

Switching from embed to object:

Let's take a closer look at the attributes of the <embed> tag example from above. I've separated them by linebreaks to make them easier for us to read:

<embed

type="application/x-shockwave-flash"

src="http://picasaweb.google.com/s/c/bin/slideshow.swf"

width="144"

height="96"

flashvars="host=picasaweb.google.com&hl=en_US&feat=flashalbum&RGB=0x000000&feed=http%3A%2F%2Fpicasaweb.google.com%2Fdata%2Ffeed%2Fapi%2Fuser%2FJonah.Chanticleer%2Falbumid%2F5220445689687444257%3Falt%3Drss%26kind%3Dphoto%26hl%3Den_US"

pluginspage="http://www.macromedia.com/go/getflashplayer">

</embed>

The good news is that three of the six attributes (width, height and type) translate directly into attributes with the same names for the new object tag. We are off to a good start.

<object type="application/x-shockwave-flash" width="144" height="96">
<!-- TBD -->
</object>

By reading the HTML reference page for the object tag (http://www.w3schools.com/TAGS/tag_object.asp), we can figure out that the src attribute doesn't exist-- the closest equivalent to it is the data attribute. The two remaining attributes, pluginspage and flashvars, do not have any counterparts in the attributes of the object tag. We will need to pass them directly as run-time parameters with the param tag (http://www.w3schools.com/TAGS/tag_param.asp).

It is worth noting this one important note from the object tag reference: "The object support in browsers depend on the object type. Unfortunately, the major browsers use different codes to load the same object type." In other words, all standards-compliant web browsers will recognize the object tag, but they may do so in different ways. Netscape-family browsers might respond to the data attribute, while Internet Explorer will not see that same information unless it is passed along in a param value.

Fortunately for us, Drew McLellan's article in the Nov. 9 2002 issue of A List Apart, "Flash Satay: Embedding Flash While Supporting Standards," gives us just the perfect trick to make the object/param sandwich that works with both sets of browsers. Check it out below:

<object type="application/x-shockwave-flash" width="144" height="96" data="http://picasaweb.google.com/s/c/bin/slideshow.swf">
<param name="movie" value="http://picasaweb.google.com/s/c/bin/slideshow.swf" />
</object>

(Mr. McLellan, if you should find yourself in the DC Metro area, drop me a line-- because your first beverage is on me. This bit of brilliance saved me time and a headache!)

Let's stay on track-- because we haven't finished just yet! We still need to create param tags for the pluginspage and flashvars, and nest them between the object tags.

<object type="application/x-shockwave-flash" width="144" height="96" data="http://picasaweb.google.com/s/c/bin/slideshow.swf">
<param name="movie" value="http://picasaweb.google.com/s/c/bin/slideshow.swf" />
<param name="FlashVars" value="host=picasaweb.google.com&hl=en_US&feat=flashalbum&RGB=0x000000&feed=http%3A%2F%2Fpicasaweb.google.com%2Fdata%2Ffeed%2Fapi%2Fuser%2FJonah.Chanticleer%2Falbumid%2F5220445689687444257%3Falt%3Drss%26kind%3Dphoto%26hl%3Den_US" />
<param name="pluginspage" value="http://www.macromedia.com/go/getflashplayer" />
</object>

Encoding the ampersands:

Almost done. There's only one thing left to fix-- those pesky ampersands assigned to the "value" attribute of the param tag named "FlashVars." This is literally a case of substituting "&amp;" for "&", as I've done below (bold removed for emphasis):

<param name="FlashVars" value="host=picasaweb.google.com&amp;hl=en_US&amp;feat=flashalbum&amp;RGB=0x000000&amp;feed=http%3A%2F%2Fpicasaweb.google.com%2Fdata%2Ffeed%2Fapi%2Fuser%2FJonah.Chanticleer%2Falbumid%2F5220445689687444257%3Falt%3Drss%26kind%3Dphoto%26hl%3Den_US" />

Final Result:

So, our end result should look like this when we view it as an entire page (assuming XHTML 1.0 Transitional):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>. . .</title>
</head>
<body>

<!-- any content in your page that appears before the slideshow -->

<object type="application/x-shockwave-flash" width="144" height="96" data="http://picasaweb.google.com/s/c/bin/slideshow.swf">
<param name="movie" value="http://picasaweb.google.com/s/c/bin/slideshow.swf" />
<param name="FlashVars" value="host=picasaweb.google.com&amp;hl=en_US&amp;feat=flashalbum&amp;RGB=0x000000&amp;feed=http%3A%2F%2Fpicasaweb.google.com%2Fdata%2Ffeed%2Fapi%2Fuser%2FJonah.Chanticleer%2Falbumid%2F5220445689687444257%3Falt%3Drss%26kind%3Dphoto%26hl%3Den_US" />
<param name="pluginspage" value="http://www.macromedia.com/go/getflashplayer" />
</object>

<!-- any content in your page that appears after the slideshow -->

</body>
</html>

Extra Credit:

Now that your page validates, you can congratulate yourself on a job well done-- until the next time you need to create a new slideshow code snippet in Picasa. Or someone else tries to use the invalid slideshow code on their webpage, and asks you to help them fix it. Or doesn't ask you to help them fix it.

The truth is what I've shown you here is only a band-aid, not a cure for the real problem. There are many tools out there which generate invalid HTML code snippets; Google's Picasa is only one of them. If you care about web standards and want to see them realized on the World Wide Web, you need to contact the developers responsible for popular tools like Picasa (http://www.google.com/support/forum/p/Picasa/thread?tid=3ca39d2989c708ac&hl=en) and persuade them to make changes that will result in the output of valid HTML code. Be patient and polite when you advocate for these changes, not pushy and critical. You never know the circumstances behind the development of an application or product. It may be the developer created the code before a specific standard had been fully ratified, or the developer may have been forced to use third party libraries/components that generate invalid code. By advocating and educating in a positive manner, you can give a developer the motivation, desire and information they need to do the right thing.

Acknowledgments/Further Reading:

Thanks to E.G. for bringing this whole Google Picasa puzzle to my attention in the first place.

Drew McLellan, "Flash Satay: Embedding Flash While Supporting Standards", A List Apart, Nov. 9, 2002, http://www.alistapart.com/articles/flashsatay

Picasa Web Albums Help Forum (http://www.google.com/support/forum/p/Picasa/label?lid=051816230f0ec560&hl=en)

Friday, October 23, 2009

PearLyrics, Harmonic, and the lyrics hunting software genre

A few years ago, I found a nice tool for iTunes/Mac OS X called PearLyrics. It would monitor what was playing now in iTunes, connect to various Internet servers and search for the lyrics of the current song. It was efficient & quick.

Long story: the record industry giants "didn't understand" and felt this tool was jeopardizing their IP, legal saber rattling ensued, etc. Supposedly it's all cleared up now, but it's worth noting PearLyrics' "hit rate" has dramatically diminished. "Sorry, no lyrics found" is the typical response; successfully finding lyrics happens about 1 time in a dozen.

I took a look at other similar software, and found even Harmonic (which is linked from Apple's downloads site) seems to have extraordinary difficulty finding lyrics for the majority of songs in my library. Makes me wonder what's changed on the Internet behind the scenes. And who made those changes.

Saturday, September 19, 2009

LG Xenon, Sending Multiple Pictures via Bluetooth Simultaneously (the video clip)

A few weeks ago, I wrote an entry about how to send multiple pictures simultaneously via Bluetooth from an LG Xenon. I figured it was esoteric and obscure, but it quickly became my number one content page-- so I guess there must be other LG Xenon owners who are scratching their heads trying to figure this puzzle out as well.

Since I had a difficult time writing the entry in a way that I felt was clear and descriptive to readers, I decided to make a mini-tutorial video which walks you through the process. I hope you find it helpful.

Wednesday, September 9, 2009

Filemaker IWP and the Database Homepage

(NOTE: I'm not writing this blog entry for my "audience"; I'm writing it to help me remember this issue a year from now. I do that sometimes. Don't worry, you'll get used to it.)

So the other day, someone shows me a specific Filemaker database being served up via Instant Web Publishing (aka IWP). They ask me to help them make a modification on the webpage that people land on when they logout/exit the application. No problem, I figure-- it's obviously a static page because it already contains customized content on it. All I have to do is drill down into the IIS webroot subfolder, locate the page, open it up in Notepad and make the requested changes.

Naturally, the customized database homepage could not be found in the webroot folder. I searched for it manually, I asked Windows to search the folder and subfolders based on strings of text we could see in the page via the browser. Nothing, nada, zip.

It turns out that when you customize the default database homepage for Instant Web Publishing, you do so by creating a file called iwp_home.html. However, you don't put this file in the webroot folder of your web server. This file resides inside a subfolder of the actual Filemaker folder itself, which is completely outside the webserver's normal document path. It's basically the lone exception to the "all static pages can be found in the wwwroot folder" rule.

Fool me once, shame on you. Fool me twice, shame on me. ;)

Saturday, August 8, 2009

LG Xenon: Transferring multiple pictures via Bluetooth

If I had to pick one feature of my LG Xenon phone as my favorite, it would be the Bluetooth connections with other devices. Sure, there's the obvious wireless/hands-free earpieces, but when I can download MP3 podcasts via iTunes on my iBook and then batch transfer them to my phone without having to keep track of another USB cable, that's when it becomes cool and begins to border on outright magic.

So, when I found myself unable to transfer multiple photos simultaneously from my LG Xenon's "Camera Album" software, something wasn't making sense. It was weird-- I could view the photos in thumbnail mode, and when I invoked the Send -> Bluetooth option from the menu, all of the pictures had checkmark boxes (not radio buttons) beside them . . . implying it was somehow possible to select more than one. But whenever you tried to click the second, third, etc. photos, the checkmark would be removed from the checkbox of the previously chosen photo.

Very. Frustrating.

I looked through my phone's manual, figuring there'd be some sentence instructing me to hold down the shift-key-equivalent on my QWERTY keyboard-- unfortunately, the section of the manual dealing with the Camera Album was very sparse. It was basically a listing of the menu features with no explanation beyond into each feature. Apparently, there is no way to select multiple photos within the "Camera Album" and send them via Bluetooth to another device.

Fortunately, there is a separate application entirely called "Pictures" located within the tabbed organizer section of the LG Xenon screens. (From the Home screen, click on the blue square with the four white dots-- you can't miss it. It's at the bottom of the home screen and looks like a six-sided die with four pips showing. You should now be looking at a rolodex-style screen, with tabs running across the top: a phone handset, one of those director's snap/cue boards, an overstuffed file folder, and a gear. Click on the tab with the overstuffed file folder, and you'll see an icon for an application called "Pictures.") I'll try to include a picture later, if I'm able.

Once you open the Pictures application, you should see thumbnails of all the pictures stored on your phone. This includes pictures you have taken as well as the various wallpapers and icons that came with your phone. Hit the menu icon in the top right corner, and select "Send." From there, hit "Bluetooth." You'll see all your thumbnails again, but only the photos you've actually taken will have those (infuriating?) checkmark boxes. Hit the "All" button on the right hand side of the screen-- this will only select all the photos you have taken, not the wallpapers, etc. Now, hit send, and choose your destination from the "Paired Devices" screen.

Hopefully this little tip might save another LG Xenon enthusiast some time and trouble. If so, drop me a line in the comments, so I'll know if other people found it useful.

Saturday, July 4, 2009

Primary Keys Must Be Independent

A Cautionary Tale About Primary Keys:

Once upon a time . . . there was a developer named "Bryan", who was assigned the task of creating a relational database application that tracked the relationships between buildings and the people assigned to them. It was an easy enough application, with only two tables-- People and Sites. When creating the Sites table, "Bryan" thought it would be okay to use the payroll code for each site as his primary key. After all, each site had its own unique payroll code. Why not just repurpose that code for the database key as well?

Several months later, one of the sites decided to open a satellite office. Since it was a different physical location, it needed its own record in the Sites table-- but since it was a satellite office, they used the same payroll code to pay their staff as the parent office. When "Bryan" tried to add the new office to his database table, he received an error message because his primary key wasn't unique. But, he couldn't just modify the entry by appending a suffix either, because that would mess up queries and reports in his application.

The moral of the story is:

Make sure your primary key(s) are not entirely dependent on a single piece of data with which other human beings can screw. If you absolutely must use something like payroll code in the example above, make it a compound primary key that combines the payroll code field with a unique, independent field.

Tuesday, June 23, 2009

Working with ISO 639-1 language codes in Coldfusion

Recently, I've been working on a project that uses the ISO 639-1 language codes (example: en = English, es = Spanish, etc.) to filter the results of a search. Everything works fine, until someone accidentally provides an invalid language code and no results are returned.

It's a subtle bitch of an error, when you think about it. Someone believes they are performing a search for any item written in Spanish that mentions the movie title, Fight Club, but they've accidentally provided the non-existent language code of "ed." No results are found because there isn't even an "ed" language, and our user walks away with the mistaken impression that hispanics have nothing of interest to say about Fight Club.

(I know this example seems silly, but there's a convention of using movie titles as examples in Coldfusion training. Try replacing "Fight Club" with "Election" and "hispanic" with "iranians." Doesn't seem nearly as silly now, does it?)

Clearly, if someone provides an invalid language code for a search, we want to notify them there is a problem. Then they can correct the language code and perform the search again. This problem actually consists of two parts: determining if the provided language code is valid, and then how to best notify the user if it is not.

Since the first part of the problem is more interesting to me, and far better coders have written fantastic articles and many blog entries on error handling in Coldfusion, I'm going to focus on the challenge of how to best determine if a provided language code is valid. (For the "UI expert" who reads this and says it's a non-issue because I should just display the language codes in a select/option list-- what if I don't have exclusive control over the interface because it's a web service or component with remotely accessible methods and third party developers are creating their own clients for it?)

Here's my first stab at code that compares ARGUMENTS.lang (i.e. the language code provided by the user) against the official letter codes:
<!--- first we need a list of the various ISO 639-1 language letter codes --->
<cfset VARIABLES.ListISOCodes = "aa,ab,ae,af,ak,am,an,ar,as,av,ay,az,ba,be,bg,bh,bi,bm,bn,bo,br,bs,ca,ce,ch,co,cr,
cs,cu,cv,cy,da,de,dv,dz,ee,el,en,eo,es,et,eu,fa,ff,fi,fj,fo,fr,fy,ga,gd,gl,gn,gu,gv,ha,
he,hi,ho,hr,ht,hu,hy,hz,ia,id,ie,ig,ii,ik,io,is,it,iu,ja,jv,ka,kg,ki,kj,kk,kl,km,kn,ko,kr,
ks,ku,kv,kw,ky,la,lb,lg,li,ln,lo,lt,lu,lv,mg,mh,mi,mk,ml,mn,mr,ms,mt,my,na,nb,nd,
ne,ng,nl,nn,no,nr,nv,ny,oc,oj,om,or,os,pa,pi,pl,ps,pt,qu,rm,rn,ro,ru,rw,sa,sc,sd,se,
sg,si,sk,sl,sm,sn,so,sq,sr,ss,st,su,sv,sw,ta,te,tg,th,ti,tk,tl,tn,to,tr,ts,tt,tw,ty,ug,uk,
ur,uz,ve,vi,vo,wa,wo,xh,yi,yo,za,zh,zu">

<!--- checking to see if the optional ISO language code parameter was passed --->
<cfif isDefined("ARGUMENTS.lang")>

<!--- if so, we use ListFind to search for provided lang code in our list --->
<!--- if ListFind returns 0 then we know it didn't find the specified code --->
<cfif (listFind(VARIABLES.ListISOCodes,ARGUMENTS.lang) IS 0)>

<!--- this is where we throw an error, or set our returnvariable to some warning flag/message --->

</cfif>
</cfif>
Sure, it gets the job done. But I think I can do better.

Tuesday, April 14, 2009

Five things you should [but probably will not] do after installing Tomato firmware

You've installed Tomato firmware on your Linux-based router/AP and tried out all the "sexy" features. Maybe you've been obsessing over your bandwidth statistics? Or used the SSH daemon to surf the web with encryption at public WiFi spots? Or perhaps found the best channel to use for your wireless network with the wireless survey tool? Chances are, you've got your Tomato configuration features customized to maximize your situation.

And I'm sure you remembered to back your router configuration up too, right?

Yeah, me neither. ;) Don't worry-- I'm not writing this entry to "look down my nose" at people. It's easy to get carried away and overlook the mundane basics when a free download adds so much utility to your residential networking gear. Now that my initial infatuation period has passed, I'm hoping to create a basic checklist of configuration tasks to help me stay more focused and disciplined during future Tomato installation/configuration opportunities. Hopefully someone else will benefit from my oversights.

1. STAY CURRENT WITH NEW FIRMWARE: Many people download and install the Tomato firmware on their router, only to forget about it after a few weeks of experimentation and customizing. It's human nature-- if something works well, we take it for granted and focus our attention elsewhere. A few months pass, and suddenly we're missing out on great new features that would make our network situations even better. Or, in a worst case scenario, we continue using an older version of the firmware that turns out to have a security exploit in it.

You don't have to check Polarcloud's website religiously every day to see if a new firmware version is available. You can sign up for their email alert service (for the "traditional" crowd) or their RSS feed (for the "cool kids" and their aggregators).

2. TELL TOMATO WHERE TO KEEP YOUR BANDWIDTH MONITORING DATA: I don't personally understand the appeal, but people like Tomato's bandwidth monitoring feature. I guess if you have an Internet Service Provider that charges for bandwidth used instead of a flat fee that it could help settle a dispute and "keep folks honest." Unfortunately, Tomato keeps your bandwidth history in temporary memory by default. This means your historical bandwidth data disappears if your router reboots for any reason (brief power outage, configuration change that required a restart, etc.)

If you need to hang on to that data, you need to tell Tomato to keep it in a less volatile place (Administration -> Bandwidth Monitoring). I personally have Tomato saving my data into NVRAM on the router itself once per week, but then I don't have an serious need for that information. If you do, you might consider using CIFS to copy the information to a computer on your network instead and saving more often.

3. USE OPENDNS: This tip isn't Tomato specific, per se, but I think it's worth mentioning anyway. You should, at a minimum, seriously consider changing your router's DNS server settings to those provided by Open DNS. Although many people talk about the improved speed they've seen since making this change, my reason for recommending them is more security-based. Thanks to a collaborative relationship with their sister-site, Phishtank, people who use Open DNS are automatically protected from blacklisted phishing sites. By using Open DNS servers in your router's settings, any computer or device that accesses the Internet via your network enjoys that same protection.

If you like that nifty little trick, signing up for a free account with Open DNS gives you even more features and control. You will want to set up Open DNS as one of your two Dynamic DNS options in Tomato (Basic -> DDNS) to keep the service informed of any IP address changes.

4. BACKUP YOUR ROUTER CONFIGURATION: You've invested time and energy learning Linux esoterics to customize your configuration precisely how you want it. The sense of accomplishment you're feeling now won't be there when you attempt recreating that configuration from scratch because "something happened and you didn't make a backup."

Save yourself the frustration by making a backup copy of your masterpiece (Administration -> Configuration) before "something happens."

5. NOW IT'S YOUR TURN! I am sure there are more than just five "essential" configuration tasks to the Tomato Firmware. Share your "sadder, but wiser" configuration story as a comment, so everyone can learn from it.

Sunday, April 5, 2009

Why I want to learn calculus

Some people love mathematics. They see an inner beauty to the subject that inspires them into learning even deeper mysteries. Unfortunately, I am not one of those people. I was "good enough" at math as a student, but it was never a burning passion. My last math class as a high school senior was Algebra II/Trigonometry. Although I had college professors who tried to persuade me to major in science, but my personality and temperament seemed better suited to the liberal arts majors. Basically, I chose Shakespeare over Newton.

Now that I am middle aged, I find myself examining recent historical events and wondering for the first time if not taking calculus may have been a mistake.

Explaining my newfound interest in calculus is difficult. It's not as simple as "something bad happened to me Wednesday, and when I woke up Thursday morning I decided I wanted to learn calculus." It's more like memories and experiences accumulated into a sufficient mass, and then a dear friend of mine began taking calculus, and that triggered this new obsession of mine.

This story may help:

I remember hearing in an astronomy course about scientists who discovered a new planet (Pluto, perhaps?) that could not even be seen from Earth. The way I understood it, they compared the mathematical model of a known planet's orbit against the actual orbital data-- and found it didn't match. That meant another factor was involved that they hadn't accounted for-- namely, the gravitational force of another planet. By analyzing the orbital information of the planet they could see, they eventually deduced the existence of the previously undiscovered planet. (Before someone pipes up with the obvious-- I know Pluto is no longer officially considered a planet; that has no bearing on the point of the story and the impact it had on me.)

That ability to see the unseen and be aware of them impinging upon your environment-- that's a theme straight out of literature and the occult. That additional sense of awareness can be empowering. It lets you make decisions that other people, who are still unaware of "external force X", fail to understand. If you doubt that, just think doctors who learned about the "germ theory" and began washing their hands before they delivered babies.

I've personally witnessed my fair share of human dishonesty. I've learned about an admin assistant at a local university who wrote "add slips" that allowed her friends to completely bypass the admission process for honors curriculum courses. When the circumvention of the process was discovered, instead of disciplining the admin assistant, the administration chose to cover it up and secretly slander the student who brought it out in the daylight. So much for the honor code, I guess. I've read through too many news stories about Kenneth Lay's Enron strategies, price fixing schemes amongst the record labels, election tampering scandals, etc. I suspect more such abuses will become known as historians decide what to make of our first decade of the 21st century.

You don't need to be a mathematical genius to know people will try to cheat other people, if they believe they can get away with it. But you might *need* to be a mathematical genius to know if a specific person or organization is cheating you now. If calculus can tell you what the orbit of a planet should be, and you can observe a discrepancy that suggests interference by an outside, unseen force, maybe you can use the same principle to detect unseen manipulation occurring with energy prices (Enron), your download bandwidth speed (ISPs and net neutrality), or stock market scams (Bernie Madoff).

So apparently my motivation to learn calculus is fueled by my inherent distrust of human nature, and my paranoia to expose fraudsters who prey on others?

Wednesday, March 4, 2009

A little CF parlor trick with Query of Queries, CFOUTPUT and ValueList

Here's the problem. The table in our database looks like the diagram at the right. But our client/boss/customer wants to display the data on the web page like this:

Level 1: Example A, Example B, Example C

Level 2: Example D, Example E, Example F

Level 3: Example G, Example H, Example I

So how do we get from table to page? It's honestly not difficult in hindsight, but I had to combine a few items I don't often use in my daily routine to accomplish it.

The first step is to get the data out of the table so we can manipulate it. Let's start with a simple Master Query.

<cfquery name="MasterQuery" datasource="ExampleDB">
SELECT * FROM ExampleTable
</cfquery>

Now that we've created a recordset with our results, we can use the Query of Queries feature, to create smaller record subsets, based upon the value contained in Level.

<cfquery name="Level1" dbtype="query">
SELECT * FROM MasterQuery WHERE Level = '1' ORDER BY Name
</cfquery>
<cfquery name="Level2" dbtype="query">
SELECT * FROM MasterQuery WHERE Level = '2' ORDER BY Name
</cfquery>
<cfquery name="Level3" dbtype="query">
SELECT * FROM MasterQuery WHERE Level = '3' ORDER BY Name
</cfquery>

We've almost accomplished our task. We've got the results split into smaller recordsets by level. All we need is to display our results! But we can't just drop them into a typical CFOUTPUT tags with query attributes that matches our subqueries, like so . . .

<h2>Level 1:</h2>
<cfoutput query="Level1">#Name, #</cfoutput>

. . . because, as any fool can plainly see, we'll wind up having an extra comma trailing after the last item! However, we can nest a valuelist function (which turns one column of a recordset into a list, separated by the delimiter of our choice) inside a "plain" cfoutput tag:

<h2>Level 1:</h2>
<cfoutput>#valuelist(Level1.Name, ", ")#</cfoutput>

<h2>Level 2:</h2>
<cfoutput>#valuelist(Level2.Name, ", ")#</cfoutput>

<h2>Level 3:</h2>
<cfoutput>#valuelist(Level3.Name, ", ")#</cfoutput>

I'm sure plenty of ColdFusion vets would look at this and wonder why I think this is a big deal because it's the obvious solution to them. What can I say? This is the first time I've run across a legit situation requested by a client that required me to use Query of Queries, valuelist and cfoutput, so to me this is new territory.

Wednesday, February 25, 2009

Troubleshooting Tip for image files that will not open

So your "not exactly tech-savvy" friend/sibling/parent sends you a JPEG file with a plea for help. No matter what they do, they cannot get the file to open and display properly. You scan the file to make sure its not some kind of malware in sheep's clothing, and when it scans as clean you find you are unable to open the file as well.

(I've encountered this problem twice in the last month now, so I figured I'd give it a quick write up in case anyone else was getting asked to assist with similar problems.)

It's a weird thing, but it's technically possible to save images with incorrect file extensions in some software. In other words, I might be saving a Windows Bitmap file with a filename of example.jpg. (For the record, Seashore on OS X refuses to let me do this-- as it should.)

If you suspect that might be causing the problem, there's an easy way to find out. Using Notepad (or any similar text editor tool), open the file and examine the first few lines. The majority of graphics files out there will have a "header" that contains information about the image file itself. PNG files, for example, will tend to have "PNG" in the first few characters of the first line-- it's sort of like a species identifier, if you will. Windows Bitmaps will start with BMP in the first line, near the very beginning of the file.

Once you know what the file is actually supposed to be, you can alter the file extension to match the type of image file. From there, the file ought to open properly in your image software.

I know, this is probably old hat for the Photoshop/GIMP veterans-- but like I said, I've run into this problem at work twice in the last month, so there are obviously some folks out there who don't see anything peculiar about overriding the default/suggested extension for the image file with their own idea of what that extension ought to be.

Hope that helps!

Saturday, February 14, 2009

A Math/Flickr word problem

I have 145 MBs of photos I want to upload in Flickr. I've already used 5% of my monthly bandwidth. My Flickr client software automatically resizes my photos before uploading, decreasing the overall size from 145 MBs to 22 MBs. After successfully uploading the resized photos, I get a notice that I've used 27% of my monthly bandwidth.

Q1: How many MBs does Flickr allow me to upload per month?

Q2: If my client hadn't automatically resized the photos before uploading, how much of my bandwidth would I have used?

Saturday, January 31, 2009

Pimp my code ;)

In my previous (hastily written) entry, I tried to show how difficult it was to extract an undetermined URL from a string of text with the traditional FIND / MID functions in Coldfusion, and alluded to using Regular Expressions as a possible solution.

This is going to be a pretty neat trick, particularly since I know less about RegEx than I do about unicorns. ;)

I had hoped to find a copy of Ben Forta's book on the subject during a trip to my local bookstore, but no such luck. Oddly enough, there didn't seem to be any books available on RegEx in the store-- everything had to be ordered online. They did happen to have the O'Reilly Pocket Reference to Regular Expressions, though-- and I found a two page list of "recipes," RegExs put together for specific purposes, such as extracting email addresses, URLs, etc.

So, I copied the recipe for the URL down, convinced I'd found the solution to our problem.

Yes, I know-- naive of me. ;)

The problem with the O'Reilly recipe is that it uses characters that have special meaning in ColdFusion, such as the pound sign. Once I figured out how to escape the pound sign, it told me the parentheses were unbalanced. Troubleshooting the recipe was getting to be a major headache, so I wound up Googling for a RegEx specific to Coldfusion for extracting URLs . . .

and wound up at Ben Forta's blog. (Yeah, I know-- like I should be surprised!?)

Anyways, Mr. Forta offered a RegEx that works with both Javascript and Coldfusion to validate a URL-- which I've used in the REFindNoCase function example below. It's far from perfect, gentlemen-- at this point my code fragment can only find/extract the first URL in a text string, and I'll need to figure out some kind of while loop to make certain every URL has been extracted. But it's a start, right?

We start with our test string, feel free to customize for your own testing purposes:

<cfset tweet = "I like google http://www.google.com better than I like Yahoo ( http://www.yahoo.com ), but that's just me!">

Just displaying the string when the page runs, so everyone can see what we start with.

<cfoutput><p>#tweet#</p></cfoutput>

If you use the last two optional parameters of the REFindNoCase function, you can tell the function at what point in the string it should begin its search (1st character by default) as well as tell it to return a structure that contains two bits of information we need to extract the URL: the position of the first character in the match, and the total length of the matched string.

<cfset results = REFindNoCase("https?://([-\w\.]+)+(:\d+)?(/([\w/_\.]*(\?\S+)?)?)?", tweet, 1, "True")>

Then we just use the POS and LEN bits with our MID function to print out the match!

<p><cfoutput>#Mid(tweet,results.POS[1], results.LEN[1])#</cfoutput></p>

We could theoretically pass the extracted string to an API (to TinyURL, for instance), or ROT-13 it, or perform whatever arcane manips we want at this point, and then a simple REPLACE function will be sufficient to insert the modified URL back into the original string-- after all, once we've extracted the URL, we know exactly what it is now.

But, there is one interesting wrinkle I should mention. I've dumped the results variable that the REFindNoCase function generates so we can look inside of it (see below):

<cfdump var="#results#">

See how results actually contains two entries for POS and LEN? That means, I think, that the RegEx is actually finding two matches overlapping, namely http://www.google.com as well as www.google.com.

Replacing the 1 in the indexes of the arrays above with 2 seems to confirm that hypothesis.

Thursday, January 29, 2009

The URL swapping Gedankenversuch

Tip of the hat to Mr. D. H., whose Twitter question (shown below) inspired this entry.

"How could I search a string for urls, if found, do something, then replace the new url?"

I'll be the first to admit I'm not the world's greatest Coldfusion coder, but this problem is more difficult and more subtle than you'd imagine. The "replace the new URL" part is actually fairly easy, with Coldfusion's replace function. But first you need to successfully extract the original URL from a string of text, such as a twitter entry.

<!---
NOTE: this code assumes . . .

A) all valid links must start with http://
B) links have no gaps (e.g. http://www.exam ple.com/)
C) only one link per a text string, for now, please! We can add more later.
--->

<cfset tweet = "I should update my blog http://anerroroccurred.blogspot.com more frequently.">

<cfset startsAt = #FindNoCase("http://",tweet)#>

<cfif (#startsAt# IS 0)>
<!--- we can't find any URLs in this text, so do nothing --->
<cfelse>
<p>URL starts at: <cfoutput>#startsAt#</cfoutput></p>
<cfset endsAt = #FindNoCase(" ", tweet, #startsAt#)#>
<cfif endsAt IS 0>
<cfset endsAt = Len(tweet)>
</cfif>
<p>URL ends at: <cfoutput>#endsAt#</cfoutput></p>
<cfset theURLis = Mid(tweet, #startsAt#, (#endsAt# - #startsAt#))>
<cfoutput>And the URL is: #theURLis#</cfoutput>
</cfif>

Looks promising, right? That is, until you realize that people do all sorts of crazy things with URLs in text.

For example:

"My blog is at http://www.example.com. You should check it out!" (That period after the URL means trouble for our code above!)

"I have a blog entry (http://www.example.com/firstServed.htm) you might enjoy." (Great, now we have to anticipate parentheses!?)

Clearly, the find and mid functions alone aren't gonna be robust enough to handle our challenge. We need some serious black magic.

Thursday, January 22, 2009

Coldfusion is dead; long live Coldfusion

They say: "Nobody uses Coldfusion any more."

Well, almost nobody, except for Adobe, and maybe . . .

1) The US Federal Government: The Senate's inaugural site, Food & Drug Administration's Peanut Butter recall info site (pretty hard to get more topical than that!)

2) New York City's Tourism Organization web site (search engine and calendar powered by CF)

3) Crayola's Official site - yup, Coldfusion & coloring, who knew?

4) The Mayo Clinic - these guys are doctors, so I think they're pretty qualified to determine if something is dead or not.

5) MySpace - Coldfusion and Fusebox, it turns out.

6) The American Kennel Club - Who's a good puppy? Who's a go . . . erm, nothing to see here. Move on to the next item, please.

7) The Peace Corps - Travel, see the world, and help people.

8) AT&T Labs Research Division - Can you query me now? Good! ;)

9) NetLingo not only uses it - they define Coldfusion as well.

10) Urology Health.org - Take my advice, don't get into a pissing contest with a wizz wiz.

Monday, January 19, 2009

iPod Shuffle with green/orange blinking lights

I'm feeling a slight sense of deja vu. Lately, I seem to spend my time and energy fixing things. It's nice on some ways, because at least I can still set things right-- but at the same time, I feel like it gets in the way of me being creative, learning new things. And yet, here I am, searching for material for a blog entry, and what comes to mind except "Maybe I should document how I got my iPod Shuffle working again after it stopped working?"

And see, the deja vu would be because a million years ago, when I first started blogging (think before Google bought Blogger, k?) the predominant topic was the fixes and workarounds I came up with to make things work properly.

No wonder no one reads my blog then. ;)

I have a first generation iPod shuffle that suddenly stopped playing songs/podcasts entirely. When you hit the controls (play, skip forward, skip back, pause, etc.) the green and orange lights blink in an alternating pattern. According to documents I found while researching this problem, it could be anything as simple as a battery in need of recharge to as dire as "the green and orange lights of death!"

No, seriously-- that's what they called it. What, you thought I made that up?

Anyways, yes, it would have been the perfect excuse to go out and buy a new iPod or other MP3 player-- but money's been a little tight lately, you know, between the bathroom renovation and Christmas. So, I figured I'd take a crack at fixing the iPod I had first.

Basically, I was able to plug it into the USB port of my iBook, and it saw the contents of the music partition as well as the contents of the data partition. (I have my iPod shuffle "split" for Disk Mode so it can store files and documents, like a traditional USB thumbdrive.) I was able to copy all my files off of the iPod in the Finder, and then reinitialized it from within iTunes.

In addition to warning me that I'd lose all the files and media, it also (after I told it to proceed) reapplied the iPod firmware update. I typed in the "new" name for my iPod Shuffle-- which I set to my phone number, just in case I ever lose the darn thing. I decided not to activate Disk Mode at this time, and just let it store music. I copied two MP3 files across from within iTunes, plugged in my earphones, hit the play button-- and everything worked just fine.

Saturday, January 17, 2009

Work on What Matters - Great Idea, but How?

I've been hearing extra buzz lately about Tim O'Reilly's "Work on What Matters" theme, particularly now that it coincides with a paradigm shift in the administration of the US Federal Government.

Although I apparently know how to work with others, I don't know how to get others to work with me. I could bore you with examples, but what's the point? You either read it and understood it immediately (because you are there in the same boat with me), or you read it, didn't understand it and never will because it has never happened to you and never will.

I spend a lot of my time and energy helping other people realize or get closer to the end result that is important to them, but whenever "my turn" comes up-- well, it's like people hear their mother calling them home for dinner or something! They can't be bothered to listen to my point of view for three seconds.

Case in point, remember in the "early" blogging days a tool called "Greymatter?" I tried offering a suggestion to help improve that tool during a major revision period, and was pretty much kicked in the teeth by the "in crowd." The simple truth behind the demise of Greymatter is that the core team decided they knew what was best and they were mistaken. The competition (Blosxom, Movable Type, etc.) focused on things like syndication feeds, trackbacks/writebacks and features that dovetailed nicely into the entire social networking paradigm that would spark "Web 2.0." Greymatter's core team decided to show what song you were listening to when you were writing your blog entry; small wonder everyone migrated off that platform then?

So, Tim O'Reilly, Seth Godin, the conjoined ghosts of Abbie Hoffman and Timothy Leary, or some expert on human nature-- please tell me, what are hard-working, passionate, creative and technologically sophisticated people supposed to do when they are repeatedly and deliberately ostracized by communities or movements because the in crowd/clique has decided to adopt a xenophobic, gated community outlook?

(sigh) It could be worse, I suppose. At least no one is trying to kill me because of my skin color.

Saturday, January 10, 2009

The Component Conundrum

I know, I should write Coldfusion applications with components as the building blocks. I get it, really-- it's better for maintenance, code reuse, separates your content from your business process. Seriously, I understand how important it is.

Too bad I can never get it to work. :(

See, here's my problem. Let's say I want to write a CFC (Coldfusion component, for the uninitiated) that accepts an employee's ID number, queries Active Directory, and returns the employee's name, phone, email, and basic contact info. It sounds like a great idea, doesn't it? Set it up as a component, accept three arguments, the ID number, and the user id and password of the account to use for LDAP. Then, you can leverage and reuse it for all your application, or your team-mates can use it in their programs . . .

Oh, um, wait, that's weird-- the field in Active Directory for employee ID number seems to be empty. Not sure what that means. Hang on a sec, let me call the Tech Support folks.

(ten minutes later)

Well, it seems that whoever set up our Active Directory felt that using the field called Employee ID to actually hold the ID numbers was too generic and needed to be more proprietary or secure or . . . something, so they actually keep that information in ExtensionAttribute22 instead. And now, we have a component that only works with your specific and unique implementation of Active Directory.

I know, it's a grossly oversimplified example, but I think it makes the point. It's hard enough for those of us who don't think natively in object-oriented fashion to figure out what should be turned into a component, then to plan out the various properties and methods. It's downright demoralizing to discover when we try to implement them that someone has made a seemingly arbitrary change to the configuration of something with which we must interact. I mean, why don't you change the name of the mail server to "Linda" or "Lassie" or "Ficus Green Pearl", while you're at it?

I just want some consistency when I work. Not like I'm asking you to get me sharks with friggin' lasers attached to their heads.

Wednesday, January 7, 2009

Linux and Windows

Sometimes one of my Windows-using friends will ask me why I use Linux. They usually don't ask because they want to learn more about Linux, but would rather start a debate about why "their favorite" operating system is better. Personally, I think a "favorite OS" is ridiculous. It's a tool to help you accomplish something. Do you have a favorite drill bit? Or a favorite subway ticket?

However, in the interest of provoking those same aforementioned Windows-using friends, I pass along the following observation:

My Windows XP work laptop has received Windows updates for the past three days, and gave me a "C:\ drive is low on disk space" warning this morning as a result. I wound up having to delete 2 GB of space.

My Ubuntu eeePC received updates after 21 days of forgetting to check, and afterwards I actually wound up having roughly 7% more free disk space afterwards.

Yes, those Ubuntu developers are amazing. :)

Monday, January 5, 2009

Steve Jobs - what's private and what's public?

Apparently Steve Jobs made some disclosures today to combat the rumor-mongering about his health. I frankly don't get it-- and maybe the truth is I don't understand it because I just don't want to do so. The "stockholders have a right to know" argument just doesn't hold water with me because if you chose to invest in a company that depends upon the health and well-being of a specific, single individual to flourish-- well, much as I hate to say it, you made a bad choice.

It's funny, but I find myself struggling to write about this. If anything, this seems like the perfect bizarre intersection of the three things about which I'm most passionate. You have a company that produces innovative, ground-breaking *technology*-- and it engages the consumer in distinctive, brand-building ways that change the paradigms we live within (sounds like *psychology* to me!)-- but stockholders/fanboys/analysts all appear to be worried that the stock and the performance of the company hinges completely upon the health of its (CEO? Shaman? Messiah?).

It's simultaneously fascinating and revolting-- we've got plenty of financial fraudsters out there that no one gives a second glance (three letters to the SEC! Three!??), who bilk people and philanthropic organizations for millions of dollars, and yet we feel we have the right, the entitlement to compel personal health disclosures from a guy who's really busted his ass to make the world a better place.

Friday, January 2, 2009

Camera and Batteries

I've been having the strangest problem with a Pentax camera recently. It's a very nice camera in terms of features and pixels, but it DEVOURS batteries. Or, at least, I thought it did.

Basically, if I recharge my NiMH batteries and put them in the camera, but don't use it right away-- when I get around to turning the camera on hours later, it will complain that the batteries are depleted. Okay, it was just a quirk of the camera I had to remember-- for some reason, it uses up batteries even when it isn't on. Not a big deal, just remember to carry the batteries separately in the bag and put them in right before use.

Except, when I was on vacation, I forgot to take a pair of batteries out for overnight. And the camera worked fine the next day. So, obviously the camera isn't to blame. At this point, the trouble-shooter in me has become intrigued. Perhaps it's the batteries? After all, the batteries I'm using have been through lots of cycles, charging and discharging. Maybe they just can't hold enough energy to reliably power the camera.

So, I buy brand new rechargeable batteries, charge them up over night-- and the camera eats them up in seconds. I charge the batteries again, figuring maybe I didn't leave them in long enough. At first, the camera is happy-- and then, less than 12 hours later when I go to actually use the camera, the batteries fail on me again.

At this point, I'm starting to get a little peeved. Brand new batteries, and the camera worked once with another set of the older batteries. So, what's left? The only other piece I can think of in the equation is the charger.

I wound up borrowing my mother's battery charger for a bit, recharge the batteries, and so far the camera has been working just fine.

So, it's got to be the charger. Right? Not so fast, because there's a curve ball. I successfully used the old charger when we were away on vacation. The real test is for me to use the old charger, with the old batteries, but on a different electrical outlet.