This blog entry is not about Hyype.net though. This is about building Hyype.net, and more specifically, about my experiences with Helma Object Publisher. Helma is a web-development framework using Mozilla's Rhino JavaScript as the server-side language.
Disclaimer: I am not an expert at using Helma. There may be better ways to organize things. Hopefully, some of the Helma guys will step in and point out where I did things the stupid way. This entry is about a newbie's experience learning the framework and building something useful with it.
Getting Started
After having struggled with installing a number of different frameworks, Helma was absolute bliss to get up and running. Inside of a minute I was able to download it, configure it, and get it running. At that time, I was presented with this:It was a powerful feel-good moment. Not only was it easy to get up and running, but I was presented with something that was actually useful instead of some stupid splash page. This page also has links to a tutorial and documentation.
The tutorial was a great place to start, and after an hour of going through that, I felt like I knew what to do.
HOPped up on objects
At about this point, my romance with Helma began to sour. What exactly is a HopObject? Why is there 'Root' and 'Global'? Why the @#$@! do my macros work here, but not there?The organization of a Helma project at first seems bizarre. Combined with some subtle differences in how functions work (Why doesn't renderSkin() work like this.renderSkin()?), I slowly developed the feeling that Helma was well-polished on the surface, but buggy and horribly organized. I compared it to PHP, which is about the dirtiest insult you can hurl at a Java-land framework.
And then, all of a sudden, I got it. The pieces clicked together, the organization made sense, and I was left reeling. I had to lie down for a minute until the world stopped spinning.
Here is the key thing you need to understand about Helma: it is an object-oriented framework.
That might not sound like a big point. After all, most frameworks today are built with object-oriented languages and overrun with objects. But that is not quite the same thing. Take Rails, for instance. It has a clean organization with model objects, controller objects, and views (which are probably objects too—I don't really know).
In contrast, each Helma application is one massive, amorphous object. It is like the difference between classes and prototypes. This is the type of organization that makes complete sense if you are thinking in a JavaScripty way.
One subtle distinction about JavaScript programs is that each one runs in the global object. It has no name, we never really see it, but it is there. (This was a crucial point when I was building JOMP—top-level functions are methods of the global object and need no special handling). Helma has just taken this idea and build a web framework on top of it. My master's thesis spends a great deal of time extolling the benefits of JavaScript's object system, so I was a little embarrassed that it took me so long to grock Helma's organization.
Each folder in Helma corresponds to some type of HopObject. These folders will hold the database configuration (if needed), the actions (more on those later), the methods, and the skins (html templates).
Global is the global object where all JavaScript objects live. Before I understood it properly, I had a fair amount of the skins stored there. Now, I have only a single macro. I'm not really sure I even need that. This is probably good form — global===bad, as Douglas Crockford would say.
Root is the single massive object that is the web application. This was the single most crowded directory for Hyype.net. Anything that did not clearly belong to a single object ended up here.
There is also HopObject, which serves as the parent object of all other Helma objects, including Root itself. I probably should have used this more, but this directory is empty for Hyype.net.
Every other directory corresponds to some object specific to your application. As it turns out, Hyype.net really only needed 3: Users, Profiles, and Thread.
For the rest of this article, remember that everything is owned by some object. A skin or an action belongs to some object—HopObjects are not passive lumps of data like they are in other webdev frameworks.
Persistence
The core use of objects in almost any web framework is to serve as containers for data. This is easy to do in Helma. It does take some configuration, but that is pretty minimal. Here is the User configuration for Hyype.net:This is a little more work than it would be in Rails, but it is not too bad. It would be nice if Helma could generate a base config, but it would really only save a minute of work. Better yet would be if it could default to the same names as the DB fields. The associations are also a little trickier than in Rails, but not bad. The documentation is fantastic, however, so I was able to figure things out without too much trouble.
_db = myDataSource
_table = user
_id = id
username = username
password = password
email = email
verified = verified
createdAt = created_at
updatedAt = updated_at
profile = object (Profile)
profile.foreign = user_id
profile.local = id
One note: In good Helma form, the database field names should have been in all caps. I ignored that, but it would have made the configuration a little clearer. With all lowercase, it is not as obvious which side is which.
HACking something together
Every action corresponds to a url. While there are different ways to do it, the standard approach (and the one I used) is to create a separate .hac file for each action. Here is a simple one:res.data.title = "About Hyype.net";
res.data.selected = "about";
res.data.body = this.renderSkinAsString("about");
this.renderSkin("layout");
"res" stands for "response". The data property stores the variables that your template will need. The renderSkin methods will write out the templates.
I would have liked Rails' style layouts. If there is a built-in setup for this in Helma, I could not find it. Rolling my own was easy enough though. I did this by creating a skin cleverly named "layout". Within it, there is a body variable. Using "renderSkinAsString" I could fill out this spot and then render the layout itself.
Skins
My last project involved JavaServer Faces. Before that, it was a PHP project. They represent the two extremes. JSF is rigid and unfriendly (and, perhaps worst of all, not html). PHP lets you do whatever you like, even things you should never, never do.Helma's skins take a nice middle ground. They don't allow arbitrary code, but they do allow you to call macros (special JavaScript functions). The result is that no code bleeds into the html. Some html did bleed into the code, however, but I managed to keep this relatively minimal.
One really cool thing about Helma's organization is that objects can render themselves. They can contain their own skins. For example, Thread has templates for being rendered as clips (displayed on the left rail), details (for a more complete view of the thread), and myClip (for the "My Hyype" page). Within the main page, each thread is rendered with 'str += hyype.renderSkinAsString(clipSkin);' The current thread is displayed on the center of the page with 'res.data.body = selectedThread.renderSkinAsString("details");'
It seems like an unusual way of organizing templates, but I found it very effective. Unlike objects in other frameworks, HopObjects know how to dress themselves.
Here is the template for Hyype.net's threads in clip view.
<div class="bb-box"><div class="ctl"><div class="ctr"></div></div><div class="pad">
<a href="<% rootPath %><% this.posterProfileHref %>"><img src="<% this.smallPic %>" alt="<% this.poster %>" title="<% this.poster %>" class="bb-img" /></a>
Posted by<br />
<a href="<% rootPath %><% this.posterProfileHref %>" class="lnk-green"><% this.poster %></a>
in <a href="<% rootPath %>main?cat=<% this.categoryName %>" class="lnk-green"><% this.categoryName %></a><br />
on <% this.postedOn %>
<div class="comm"><a href="<% rootPath %>main?threadID=<% this.id %>&pageNum=<% request.pageNum %>" class="clip-title"><% this.title %></a></div>
<% this.agreeDisagree %>
</div><div class="cbl"><div class="cbr"></div></div></div>
Utilities
Helma offers a rich set of utilities as part of its core and a number of optional extensions. And if that is not enough for you, the Jala Project offers a bunch more modules. For the most part, these are well-documented and easy to use. The one time I had a problem, a note to the Helma mailing list quickly answered my question.One problem spot I did run into was trying to include Java code in my project. For the most part, this is not needed. Rhino allows you to call Java. However, I wanted to get the MD5 hash of a string for comparing passwords. I could not call the methods I needed in Rhino, since it was ambiguous to Rhino which method I was calling.
I wrote the method I needed in Java, but there was no good place to store the class file. Instead, I put the class file in a jar and added it to the lib/ext/ directory. This was not the cleanest process in the world, especially since it is shared by all projects, but it did the job.
Deployment
Once we had the application ready to deploy, life got a touch more challenging. We went off the beaten path a bit by not using Antville. Things went more or less smoothly until we tested out uploading images.The server crashed. We reconfigured a few things. It crashed again. After several hours of research and troubleshooting, we nailed down the problem to our version of Java. Specifically, it was using GNU's implementation, which ignored some of the flags we were passing it. I still have not totally forgiven GNU.
While I think Helma is blameless for this, it is one risk of using a framework that is not as widely used as PHP, JSF, etc. Fortunately, the community is friendly and active, which usually more than makes up for this.
No comments:
Post a Comment