|
Helma Introduction v.3
A draft for a introduction to Helma for newbies.
Helma is a scriptable web application server that is built around two powerful concepts: Transparent mapping of database items to an easy to navigate and easy to manipulate hierarchical object structure, and an elegant way to provide scripting and presentation layers to these objects. All this is done by creating a set of object prototypes that describe the classes of objects that are used in an application: in which database they are persistently stored, what scripts can be called on them and which templates and "skins" should be used to present them to the application's users.
The building blocks of your web app: Prototypes
Web applications built with Helma start with a group of standard prototypes: "root" for the website's root object, "user" for objects representing the application's users, "global" for functions and skins not belonging to any object and "hopobject" for functions and skins inherited by all of the application's objects (so to say the prototype of all prototypes). Additionally to these, applications usually define custom types which are specific to the problem the applications try to solve. For example, a content management application may define prototypes called "image", "story", "role", "comment" and so on.
Defining prototypes for your web application is straightforward: you simply create a subdirectory in the application directory with the name of the prototype. To add logic, presentation or database mapping to a prototype, you simply add files to the prototypes directory: A file called "type.properties" to define the prototype's database mapping, files with a ".js" extension to add generic JavaScript code, ".hac" for functions exposed as a Web URL, and ".skin" for layout snippets.
Since prototypes are just "object blueprints", you need actual objects to get work done. These objects are called HopObjects to make clear that they provide some extended functionality compared to standard JavaScript objects. To create and use a HopObject with a specific prototype is easy: each prototype has a constructor with the name of the prototype, so if you have a prototype called "story" and want to create an object with that prototype, you just write:
var myStory = new story();
If you know Javascript you already have a good idea of how HopObjects work. Like all Javascript objects, they have properties. A property consists of a property name and the value that is associated with that name. For instance, the code below assigns some properties to the root object (which is the root object of each Helma application - more on this later): a date, a number, a string and another HopObject.
root.createDate = new Date();
root.count = 0;
root.name = "Hugo's Home";
root.storypool = new storypool();
In addition to named properties, HopObjects have the capability of holding collections of child objects, forming a hierarchical object tree. Collections are similar to JavaScript Arrays, but in addition to be accessible by index (0, 1, 2, ...n), child objects can be accessed by id or name. Collections are managed by a simple set of functions like add(), get() and remove() which are inherited by all HopObjects. For instance, if we were to create a new story object and add it to the storypool object created above, we'd simply say:
var s = new story();
root.storypool.add (s);
Automatic object persistence using relational databases
On the database side, Helma comes with an embedded object database able to store HopObjects in their native format, as well as the capability to map these objects to relational database tables. The great thing is that the choice of storage option is pretty much transparent to the developer - HopObjects look and behave the same, no matter which type of persistent storage they use.
This also means that no explicit code is needed to access, modify or store an object from or to a database. When a persistent object is accessed by script code, Helma will transparently fetch it from the database if it isn't already in the object cache. Likewise, if a change is made to any property of a persistant object, Helma will make sure the changes are stored back to the database as soon as the request was processed successfully. This is true both for objects stored in the embedded as well as the a relational database. An object becomes persistent simply by using it as a property or adding it to the child collection of an already persistent object.
The major difference between using the embedded database and external relational databases is that for the former you don't have to specify any object mapping for your prototypes since objects are stored "as they are", for relational databases you need to describe how properties and child objects are mapped to the database tables. This is done with a file called type.properties in the prototype folder. We're currently in the process of redefining and simplifying the format of these files. These two development modes complement each other pretty well. While one lets you quickly prototype new applications, defining the database organization as you go, the other stands for a more organized and design-oriented application development approach.
Scripting and skinning
Managing object persistance is only one half of what Helma does. The other half consists in applying scripts and skins to these objects to get them to their target destination in a meaningful format.
Scripting in Helma is object oriented. This may sound intimidating if you don't have a computer science degree, but all it means is that the code is coupled to the data it works on. So if you have code that works on the root level of your web app, you put it in the root prototype; if the code is concerned with the users of your site, you put it in the user prototype; if it does something with your story objects, you put it into your story prototype and so on. The nice thing about this is that you can always refer to the "local" object as this. For example, calling the function this.href() will always return a URL that points to the object you're workin on, provided that your application is set up correctly.
The simplest way to extend the functionality of your HopObjects is by writing standard JavaScript functions. Any file inside a prototype directory with a .js extension will be interpreted as JavaScript.
Since Helma's main purpose is to serve objects over a web interface, we need special kinds of functions that are callable via a web server. These public or web-exposed functions are called "Actions" and are defined within files with a .hac extension. Action files are basically JavaScript functions without a function declaration, so any code you put inside a .hac file will be immediately called when the action is invoked over the web. The .hac extension is omitted in the URL, so to call an action called myAction in the root object, you'd simply use the URL "/myAction" instead of "/myAction.hac".
Of course a web scripting framework would be useless without templating support. What we have come up with in this field - after years of experimenting with more traditional "server pages" approaches - is a combination of "Skins" and "Macros" that provide clean separation of application logic and presentation. Skins are snippets of presentation that may include special macro tags. Macros are implemented as JavaScript functions and may in return call skins. With this approach, there isn't even a need to provide simple control structures like conditional and loop statements, since all this is handled "naturally" by the macro as part of the application logic they provide to the skins. It also allows to break down complex skins into simpler skin fragments that are easier to manage than big monolithic chunks of layout.
... comment
Page last modified on 2001-10-15 17:10 by hns
|