Tutorial - Axiom

Contents

Zero To Axiom In Thirty Minutes

What is Axiom?

Axiom is an object-oriented web application framework, like Ruby on Rails, Struts, et al. It has a built-in web server, fully searchable object database, and dead-simple zen-like application development. Rather than a compiled language like Java, Axiom applications are written purely in ECMAScript (Javascript).

Javascript? Are you high?

No, really. Javascript is a quite powerful and expressive language that has been saddled with a history of buggy and incompatible implementations in web browsers. It has a familiar, C-like syntax while being a fully functional language. It has language level support for JSON, a concise and standard object notation. Regular expressions and even XML are native datatypes. Most programmers these days have at least a smattering of Javascript and most teams won't need much time at all to get rolling with it.

Axiom uses Mozilla's Rhino engine for a fast, stable scripting platform. Because it runs on the JVM, you've got easy access to any Java libraries you put on the system classpath through the scripting layer via Rhino.

If you need further convincing, see Douglas Crockford's Javascript: The World's Most Misunderstood Programming Language.

Hello Blog

The quickest way to see what Axiom is all about is to dive in and create an application. Let's create a simple blog from scratch. This should be able to be done over a lunch break.

Setup

Requirements: You'll need Java 1.6 or greater installed. If you want to enable some of the optional advanced features of the Axiom image libraries, you'll need the ImageMagick libraries installed. You can get them at http://www.imagemagick.org .

The "installation" of Axiom is about as simple as you can get. Create an empty directory where you want to start working and extract axiom.zip inside. You'll see a couple of things here:

  • start.bat and start.sh - A pair of scripts used to start Axiom. The bat file is for Windows, the sh file for *nix environments.
  • launcher.jar - In the root directory, used by the init scripts to launch Axiom.
  • lib/ - A directory full of Java libraries used by Axiom.

Now, let's roll up our sleeves and make a blog!

A Window Into Axiom's Heart

Axiom comes with an application management interface already loaded. We'll use this to bootstrap our first application. First, start up the Axiom server by running the start script in Axiom's root directory (this is start.bat if you're using Windows, start.sh if you're using OS X or anything else *nix-ish). Then, open a web browser and go to http://localhost/axiom-manage. You should be greeted with a prompt to create a superuser account. Create one, and log into the manage interface. You'll be greeted with a prompt to create or select an application to manage.

Let's have Axiom do some bootstrapping work for us. Click the "Create Application" link. You'll be prompted to enter the name of the application. Enter "simple-blog" and click "Create". A confirmation message will appear; we've now got a blog application! It doesn't do much yet, but if you point your browser to http://localhost/simple-blog you'll see a the default welcome message. We're on our way.

The manage application is useful for much more than creating new applications. You can also:

  • Run your application tests.
  • Explore your app through an interactive JavaScript shell.

We'll return here later to explore these features.

First Look

Now, let's go take a look at the framework code Axiom has generated for us. Go to apps under the directory where you unzipped Axiom. There should be two folders: "manage" and "simple-blog". Each of these is an application. In Axiom, every standalone application is stored in apps/<application_name>. Open up the simple-blog folder. Inside, you'll see a file called app.properties and two directories, AxiomObject and Root. app.properties is where application-specific properties are set (see app.properties for a list of properties that can be set).

Prototypes

Everything in Axiom is represented by objects. The application directory is where we define the prototypes that represent these objects (like classes in Java, but not quite). You define a new prototype simply by creating a directory with the name of your object and editing a file named prototype.properties inside that directory. The prototype.properties file is a plain-text Java properties file describing the schema of the object; what properties exist on objects of this prototype, the datatypes of the these properties, and things of that nature. Let's take a look at the prototype AxiomObject's schema- open up AxiomObject/prototype.properties.

id
id.type = String
id.index = UNTOKENIZED

_children
_children.type = Collection(AxiomObject)
_children.accessname = id

Let's take a look at the first two lines. A property on a prototype's schema is declared by a single line with the name of the property. Following that line, we start defining attributes of that property. type specifies what sort of data is stored in this property. Here, it's a String. We also tell Axiom to index the id property as an untokenzied field (see prototype.properties for a more through discussion).

The next three lines set the behavior of a special property on AxiomObject. Every object in Axiom (aside from the Root object) is stored as the child of another object: in the _children collection we've set here. Setting the accessname attribute defines what property serves as the key that is used to retrieve the object from its parent (more on this later, I promise!).

Note: AxiomObject is a special prototype. Every other prototype automatically extends AxiomObjects unless they explicitly extend another prototype with the _extends directive in prototype.properties. You can think of AxiomObject as being the default value for the _extends directive. In this respect, it acts like the Object class in Java.

There's No Place Like HomePage

A blog needs a landing place to view our most recent entries, so let's create a HomePage prototype for this purpose. Create a folder named 'HomePage' and open up a prototype.properties file inside. Let's give our HomePage objects a single property: a title, which we'll store as a String.

title
title.type = String

Now that we've defined our prototype, it's time to create an instance and locate it in our blog's object tree. Go back to the manage application (http://localhost/axiom-manage), select 'blog' and click the 'JS Shell' button. You can use this interactive shell to explore Axiom's Javascript environment and manipulate the objects in your application. Now that we've defined how HomePages work, let's make one! Drop this code the shell and hit "Execute":

var home_page = new HomePage();
home_page.title = "Blog Of Awesomesauce";
home_page.id = 'home';
root.add(home_page);

We create a new instance of the HomePage prototype just like we'd create an instance of a Javascript object. We then give the title and id properties value- the id property is inherited from the AxiomObject prototype. We then add it as a child of the app-wide Root object. We don't have to lift a finger to write our new HomePage to the database- as long as an object is located within the Axiom object tree (e.g. has been added as a child of another object), the object and any changes to it will be transparently persisted at the end of the request that created or modified it.

Four lines of code to create a new object, set two properties, locate it in our application and persist it to the database. Not too bad at all. Let's go check out our newly created HomePage. Open a new browser window and go to http://localhost/simple-blog/home . You'll get an error. Don't panic!

Error in application simple-blog: action not found.
Anatomy of an Axiom URI

http://domain/[application]/object/object/.../object/[action]

  • Application Mountpoint: Axiom can have many applications running on the same server. Each of these applications is given their own URI namespaces. For example, if I were running our simple-blog application and a todo-list application on the same axiom server, the blog app would be available under http://www.mydomain.com/simple-blog and the todo app would be under http://www.mydomain.com/todo
  • Object Path: Every object in an Axiom application is located in a tree of objects, starting at the Root. Each object is accessed through its accessname- in this example, its id property. If C is a child of B, B is a child of A, and A is a child of Root, then accessing C through a URI would look like http://www.mydomain.com/A/B/C .
  • Action:An action can be either a Javascript function or a TALE template defined on that object. If no action is specified, the default action is "main".

When we access an object via its URI (see sidebar), we are invoking an action on that object and viewing the result. Axiom is looking for the default "main" action on our HomePage and couldn't find it. So, let's create a quick TALE template for the main action. Open up a file called main.tal under the HomePage directory.

<html xmlns:tal="http://axiomstack.com/tale">
    <head><title tal:content="this.title"></title></head>
    <body>
         <h1 tal:text="$">Welcome to ${this.title}</h1>
	</body>
 </html>

Now, refresh your browser and you should see the HomePage greeting you. See Axiom TALE for a full introduction to our templating language, but it's pretty easy to see what's going on here. All TALE expressions are at their core Javascript expressions that are evaluated in the scope of the current object; that is, 'this' refers to the HomePage object to which this template belongs.

Not a bad start, but a blog isn't much of a blog without something written in it.

We Need Content!

Let's a create a prototype to represent our individual blog entries. Create a folder called Entry, and open up its prototype.properties:

title
title.type = String

body
body.type = XHTML

date 
date.type = Time
date.default = (new Date())

We've given our entries a title property, just like our HomePage. The body property will store the body of the entry. Because we'd like our users to be able to enter rich text into blog entries, we'll store the body as XHTML. This means you can access the body property as a live E4X xml fragment and do all sorts of slicing and dicing with native language constructs. The date field represents the date the entry was published, and is stored as a Javascript Date object. It's given a default value when our object is created: the value of the JS expression of the right side of the .default operator.

Up in the sky! It's a Javascript function! No, it's TALE! Wait, what?

In the example here, we're pulling in our entry_content TALE file with what looks like a Javascript function call. That's because it is! Every TALE file is automatically wrapped up in a JS function of the same name. When you create a TALE file named foo.tal in your prototype's directory, a function named 'foo' is automatically added to the prototype. Calling this method returns the result of evaluating your TALE on that object.

Let's create a TALE fragment in entry_content.tal

<div xmlns:tal="http://axiomstack.com/tale">
      <h2 tal:content="this.title"/>
      <p tal:text="$">Published on: ${this.date}</p>
      <p tal:content="this.body"/>
 </div>

We'll also give them a landing page in main.tal:

<html xmlns:tal="http://axiomstack.com/tale">
    <head><title tal:content="this.title"/></head>
    <body tal:content="this.entry_content()"/>
 </html>

Now, we need a way for users of your site to write and publish blog entries without manipulating objects in a Javascript shell. Let's create create a pair of things to do that on the HomePage- a form for users to write in and service to recieve the form and make an Entry object out of it.

Go back to the HomePage directory and create a new file called compose_entry.tal:

<html xmlns:tal="http://axiomstack.com/tale">
    <head><title>Compose Entry</title></head>
    <body>
 	<form action="create_entry" method="post">
 		<label for="title">Title:</label>
 		<input name="title"/><br/>
		<label for="body">Body:</label> 
		<textarea name="body"/><br/>
 		<input value="Save Entry" type="submit"/>
 	</form>
    </body>
 </html>

Now, create another file in the same directory called methods.js:

/**
 * Create a new Entry object from the form.
 */
function create_entry(){
 		  var entry = new Entry();
		  entry.title = req.data.title
		  entry.id = req.data.title.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '');
		  entry.body = new XMLList(req.data.body);
		  this.add(entry);
		  return "Your blog entry has been saved."
}

Now, time to post our first entry! We're going to invoke the compose_entry action we just created, so point your browser to http://localhost/simple-blog/home/compose_entry . You'll get an error. Don't panic![*]

Error in application simple-blog: Unauthorized for action compose_entry.

[*] We assume that you are a hoopy frood who knows where his/her towel is.

What's going on here? When we created our main actions earlier, we sidestepped discussing a key feature of Axiom's architecture: security. All actions, aside from main, are secure by default. In order to make an action publicly accessible, we need to explicitly tell Axiom to open it up and to whom. Create a file in the HomePage directory called security.properties:

create_entry = @Anyone
compose_entry = @Anyone

This file associates action names with permission levels. For now, we'll just open up our two actions to the world; for a detailed discussion of what security levels can be set, see security.properties.

Now, point your browser again at http://localhost/simple-blog/home/compose_entry , and you'll now see our newly created form. Yay! Give this entry a title of "Hello World!" and a body of "Greetings to <em>all</em> the world!". When you hit submit, the form will post its contents to our newly created create_entry action, which will create an Entry object out of that data and attach it to the HomePage. Now, point your browser at http://localhost/simple-blog/home/hello-world and you'll see the main action we created for the Entry prototype a few minutes ago.

All Together Now

Viewing individual blog entries is all well and good, but we should able to view the most recent on the home page. Let's write some code to grab the last five entries and display them on the home page.

Insert into the HomePage's main.tal, just after the h1 element:

<div tal:repeat="entry: this.get_entries()" tal:replace="entry.entry_content()"/>

The tal:repeat directive creates an iterator named 'entry' over the items that return from the method call to get_entries. The div is repeated once for each iteration. The tal:replace directive replaces the div element with the result of calling entry_content upon the entry (remember that file we split up earlier?).

Now we need the actual get_entries method! Put this method after create_entry in the Homepage's methods.js:

/**
 * Return an array containing the last 5 five Entry objects created.
 */
function get_entries(){
     return app.getHits("Entry", {}, {sort: {date: 'desc'}}).objects(0, 4);
}

This is our first look at using the Axiom object query API. You can see the full details of this system at Query API. Here, we're grabbing all the Entry objects in the system, sorted by their date field descending, and returning the first four objects found.

Go back to the compose_entry (http://localhost/simple-blog/home/compose_entry) form in your browser, and publish a few more blog entries. Then, go back the HomePage's landing page- you should see the last four entries displayed.

We've barely scratched the surface of what Axiom can do, but that should be enough for one lunch break. When you're ready for more, check out the Tutorial Part II.