Monday, May 26, 2008

Using a Transfer decorator to get related records within the same table

 

One of the benefits of using the Transfer ORM in your application is that if you define the relationships between your tables in the Transfer XML configuration file, you can retrieve any objects related to your current object through a variety of functions.

However, sometimes you need to relate records within the same table. For example, in my current project, I have a table of item records which need to be arranged in a hierarchy of unknown depth: one set of items could have two levels of hierarchy, another three levels. Each item, therefore, can have one parent item above it (identified by a parentId field in the record) in the hierarchy and any number of children below it (and all the child records have the current item's ID as their parentId value).

How can you use Transfer to access these in-table relationships? One way is by using a decorator object. As the Transfer documentation explains, a decorator is a CFC file you write yourself that extends the TransferDecorator CFC that comes with Transfer. You associate your decorator with the Transfer object that represents the table in your Transfer configuration file so that when a Transfer object for a record in that table is generated, the functions you define in your decorator become part of that object. So say I define a Transfer object called "Item" in the package "example". Here is the XML in the Transfer configuration file:

The decorator to be called is the itemDecorator.cfc in the "com" directory. I want to have two new functions in the decorator, one that returns a Transfer object for the parent record, and one that returns an array of Transfer objects representing each child record. Here's the code:

In a decorator CFC that extends the TransferDecorator CFC, you can access the current item's Transfer object all of its functions via the getTransferObject() function, and you can access the main Transfer object via the getTransfer() function. So the single statement in the getMyParentItem() function in the decorator CFC is equivalent to:

 
<cfset parentItemObject= transfer.get("example.Item",currentItemObject.getParentId())>

The getMyChildItems() function is slightly more complicated because it needs to retrieve a recordset of child objects first, using the listByProperty() function of the main Transfer object (again, provided via the getTransfer() function within the decorator), then loop through those records, creating a Transfer object for each child record and appending it the array returned by the function.

What if the item record in question is either at the top of the item hierarchy or at the bottom? If there is no parent item, the Transfer object returned by the getMyParentItem() function will have an itemId of 0 (the default value for an empty numeric primary key) and empty values for all of the other properties, so you can test for that condition. If there are no child items, the array returned by the getMyChildItems() function will simply be empty.

This is only the second Transfer decorator I've ever written, so there may be a better way of going about it, but it gets the job done with just two short functions added on to existing object functions provided by Transfer. I like it when things are this easy.

Friday, May 23, 2008

Creating a hyperlink that works with or without AJAX

The current project I'm working on includes a display page where users can see a list of messages generated for them by the system. Each message consists of an <li> element containing the text of the message followed by a hyperlink labeled "Delete". I wanted to make it such that you could delete each message without refreshing the page, but still accommodate users who had JavaScript turned off, and without a lot of extra work. How did I do it? Like so:

...The only other thing you need to do is put a conditional statement in the page/event that is called by the hyperlink that looks for the presence of the additional URL variable ("js" in this case). If JavaScript is turned off, that additional variable will not be defined and the page/event will redirect the action back to the calling page once it's done deleting the message data from the database. If JavaScript is turned on, the page/event will simply delete the message data (no action redirect), and the user simply sees that message item disappear from the list.

Wednesday, May 21, 2008

A ColdFusion IDE feature request: tools for creating Java classes

One of the "themes" that I took away from cf.Objective() was the shortcomings of ColdFusion when it comes to generating a collection of objects. Most everyone agrees on the fact that there is a performance penalty when a CFC is translated into a Java class (or, usually, a series of Java classes) because ColdFusion is a dynamically typed language and Java is not, and reconciling that difference takes processing power.

But not everyone agrees on what the solution to this issue should be. Some folks believe that Adobe should try to improve object generation performance even more, building on the improvements that came with ColdFusion 8. Others feel certain that ColdFusion will always pale in comparison to Java when it comes to object generation and that any enterprise-level ColdFusion application should use pure Java classes on the back end. A few folks at cf.Objective() even advocated integrating ActionScript 3 (the strongly-typed programming language behind Flex) into ColdFusion for use in building objects.

Personally, I would love to see a big improvement in object performance in the next version of ColdFusion 9, but I strongly suspect that equivalent objects written in pure Java classes will still be significantly better in terms of performance despite Adobe's best efforts. If we accept that as the most likely outcome, then the issue becomes how to get ColdFusion developers without any Java background (like myself) to code objects in Java?

I think one way of accomplishing that is for Adobe to create a ColdFusion IDE that provides ColdFusion developers with an easy means to create Java classes and use those Java classes for the model in their applications. It could provide a wizard similar to the CFC wizard tool in CFEclipse that either generates the .java file based on a database table or lets the developer manually define properties and methods for the class. It could also provide a means of generating the Java class files based on an XML configuration file so existing code generators could be modified to work with this IDE feature.

Of course, creating the .java files is only the first step. The IDE should also be able to introspect any .java file and display all of the information a developer would need to interact with that resulting class (properties, methods, data types, etc.) If the developer needs to make changes to the class, they could either edit the .java file directly in a normal IDE editor window (with syntax checking), or via an editing dialog box that lets the developer make changes to the configuration of a method and the statements within the method without having to worry about aspects of the Java syntax involved in describing the method. The IDE would also take care of compiling the .java file to a class every time it is updated and reporting back any compilation errors.

Like I said, I have no Java programming background, so maybe this idea is more than a bit of a stretch. There are also probably other issues that would need to be addressed (like how to manage the dependencies between the Java classes), but I'm betting cleverer folks than I could figure out a way to make it work. Even if the tools for creating Java classes were limited in what they could do, allowing ColdFusion developers to generate their own .java files and Java classes using familiar terms and concepts and then see the results could shorten the Java learning process.

ColdFusion has a long tradition of making it easy for developers to perform certain tasks and operations. When we talk about that tradition, we tend to narrow our focus on the capabilities of the CFML language. We tend to forget that the ColdFusion server software makes our lives easy as well, letting us configure datasources, security features, and gateway processes without having to deal with those issues in our programming code. It's the entire ColdFusion package, as a whole, that makes developing in ColdFusion easier than developing in other languages.

If Adobe develops a ColdFusion IDE, that IDE should also be designed--optimized, in fact--to make developing in ColdFusion easy, regardless of the developer's skill level. So I say if our OO ColdFusion developers need to be able to create their objects in Java, that IDE should help them do that.

Wednesday, May 14, 2008

Table normalization verses long-term data storage

I'm currently working on an application that involves long-term storage of assessment data. Users submit records of their activities and assess their performance, and then reviewers look over those assessments and denote whether they agree or disagree with them. Each assessment database record is related to a reviewer through the unique reviewer id that is part of the assessment record, and I can use that relationship to retrieve the reviewer's name whenever I display the assessment record.

It's a standard example of table normalization. If the reviewer's name was stored within the assessment record itself, and the reviewer changed their name for some reason (marriage, divorce, mid-life crisis, etc.), the application would have to update the name in both the reviewer's record AND the assessment record. But by using the reviewer's id in the assessment record to establish a relationship between the assessment record and the reviewer record, the reviewer's name only needs to be recorded or updated once.

However, this project will entail keeping the assessment data for an undetermined number of years. With the data arrangement I just described, that means I would have to store the assessment records and all of the related reviewer records if I want to be able to keep showing the name of the reviewer when looking at older assessment records. That could result in keeping a lot of extra data about reviewers (addresses, e-mail addresses, logins, passwords, etc.) who are no longer associated with the program simply because we need to keep their name tied to the assessments.

I think this is one of those situations where it makes sense to repeat a little data. Recording the reviewer's name in the assessment records allows me to let the administrative users of the application delete reviewer user accounts without impacting historical data. It means a bit more work in keeping the reviewer's name the same in both records, but in the long run I think it's worth the effort.

Thursday, May 8, 2008

Some updates on reordering items with jQuery

I have a keen interest in being able to reorder items in a list and then record that change in order in a database using JavaScript, specifically with the jQuery UI Sortables component. Here's some things I've learned recently regarding the topic:

Reordering Nested Lists

In my last project, I created a tool that allowed my clients to rearrange nested lists of articles and article categories via drag-and-drop using the jQuery UI Sortables component.

My clients were very excited about the tool when they saw it in the prototype, but they ran into some difficulty when they tried to use it themselves during the final testing. They found it hard to place an item at either end of a list block because there were no visual cues outlining the borders of the list.

It was also nearly impossible for them to move an item around in the top-most list: if they tried to place it in between sibling items or at the end of the list, the item would almost always get dropped at the end of whatever sublist came at the end of the sibling item above.

Finally, the scrollable <div> element containing the nested lists would not scroll to match the drag movement of the cursor, so if they needed to move an item from one end of the nested lists to the other, the item would have to moved in steps.

I solved all of these problems by abandoning the drag-and-drop technique and replacing it with a version of my "click-to-click move" technique. So now when they need to move an item, they click on an icon associated with each item (hidden until needed), and target <div>s are created above and below each item:

They then just click on the target for the place they want the item moved to, and the item is "moved" and the targets are removed (technically, the item is cloned, the clone is put in the new position, and the original item is deleted).

They like the "click-to-click move" version a lot better, so perhaps that's a better means of reordering items when using nested lists. But I recently discovered jTree, a brand-new jQuery plugin someone wrote specifically for sorting nested lists using drag-and-drop. I tried out the demo for it, and it has visual cues to help you position items in the right list, but it does flicker a bit at times. Still, I might play with it the next time I have to deal with nested lists again.

Reordering Table Rows

Lists are nice, but they don't cut it when each item is really a collection of separate items: that's when you need a table. In my current project, I needed a way to reorder the rows in my table as a means of letting the user reorder the data records. The jQuery UI Sortables component, however, does not work on table rows.

I tried to create a pseudo-table by using <div>s within each <li> item to evenly space the data fields in each row. I got it working and looking slightly less than ugly in FireFox, but it didn't fare so well in IE.

I then went browsing through the jQuery UI discussion board on Google Groups and found a post that mentioned a plugin for sorting/rearranging table rows.

It's called TableDnD, and it's a slightly different approach to the challenge. When you click on a row and drag it, there is no drag outline and no gradual movement: if you move up far enough, the content of the row you're dragging and the row above it quickly switch places, and vice-versa if you move downward. It highlights the moving row as you move it so you can keep track of what's happening. The movement is very smooth and you can go as fast as you like.

The only drawbacks I'm aware of so far is that the dragging action will not trigger the scroll bar if you're trying to drag the row beyond the visual limit of the window or scrollable container (much like my original nested list tool), and someone pointed out to me that if you fill all of the cells in a row with other DOM elements such that you cannot click on any of the cells themselves, you won't be able to grab the row and move it. That's not a problem your table cells just contain text, but it's something to keep in mind if you plan on formatting the text with <span> tags or providing input boxes for the user to edtit the cell data.

Tuesday, May 6, 2008

Reflections on cf.Objective()

Like I said in my comment on his blog post, I don't think I can top Ben Nadel's description of the experience of being at cf.Objective().

Still, there are a few things I want to say for myself. First of all, a big thank-you to everyone involved in the planning and execution of the conference. You all did a remarkable job. Same goes for the speakers. I think everyone learned a lot at the conference.

But what really made this conference special to me were the conversations I had outside of the presentations. I've been to CFUnited and even to MAX before, and while I learned a great deal at both, I tended to feel a bit lost, being a solo developer amongst a large crowd of folks who knew each other already or came with colleagues. cf.Objective() was different: it never took more than a minute or two to get involved in a conversation with folks from the blogosphere or even developers I knew nothing about. I always felt like I was part of what was going on. It was truly the most enjoyable conference I've ever attended.

Like I alluded to in my earlier post, I'd been feeling down and negative about, well, a number of things in the weeks leading up to the conference. I feel now like I'm back on track, ready to take what I got from this experience and run with it. So stay tuned.

P.S. A big thanks to Adobe for bring MXNA back to life, better than ever!

Thursday, May 1, 2008

Off To cf.Objective()

...And not a moment too soon! I've been in a bit of a rut lately, so I'm hoping the conference and my fellow conference-goers will help re-energize my enthusiasm a bit. I doubt I'll be blogging much from the conference: I tend to write slowly and with the MXNA blog aggregator still down for the most part, I'm not sure that anyone would "hear" me anyway. So if you want to keep track of what's going on at the conference, tune in to the bloggers covered by one of the other aggregators like FullAsAGoog, Feed-Squirrel, or ColdFusionBloggers