Installation of development environment
Getting software
Here is the link to our download center. You need to download Asyncode Development Kit
You need to install MongoDB as described here (at the bottom of the page are how-tos for Unix operating systems which are much better solutions than tar.gz):
http://www.mongodb.org/downloads
You will also need to install Mongo's Python driver (found at the bottom of the page).
Additionally you may download Komodo Edit which is the great IDE for webapp development available at http://www.activestate.com/komodo-edit/downloads
Configuring
In most cases you do not need to configure MongoDB.
WARNING! If you run MongoDB in OpenVZ container (popular on VPS solutions) you need to set the virtual memory limit by issuing command:
ulimit -v <3/4 of your RAM in kilobytes>
and start mongod with:
mongod --nssize 2 --smallfiles
This will cut maximum size of your data to the specified value, but for small sites it should work.
The ACR installation process for development purposes is not automatic as of now, but is really easy. Just unpack tar.gz archive anywhere you like.
Running
To start Runtime run:
python asyncode.py
Default behavior of ACR is to run in multiproject mode with project directory set to <HOME>/projects (changeable through -P parameter). This brings easy way to run multiple projects on the developer computer. ACR connects to MongoDB running at localhost.
In multiproject mode copy projects directory from ACDK to your home directory and rename (or create symlink) hello project template to localhost.
Go to http://localhost:9999/ - you will see "Hello World!" message.
Project directory structure
- <app-domain> - your application root directory:
- config.xml - main configuration file of application - see Configuration section
- views - BLSL view files:
- api - this directory contains apps public API BLSL files,
- static- static files including Asyncode Frontend (XML, XSLT, CSS, JavaScript, images):
- XSLT - the XSLT-based core of AC Frontend or custom XSLT files,
- texts/<lang>.xml - files with localized texts of UI,
- css - CSS files:
- style.css - default css file of app; always included in the output HTML,
- js - Javascript files:
- init.js - default javascript file of app; always included in the output HTML.
Beside these files, AC Frontend uses external files hosted on some CDNs including:
- reset-fonts.css - YUI CSS file which unifies browsers default CSS rules and adds font sizes definitions,
- grids.css - CSS file containing styles for AC Frontend template language,
- yui.js - base of YUI3 Javascript Library,
If you do not want to use them, you need to edit index.xsl file manually.
Blog specification
Here is a minimal set of features that blog must have to actually call it a blog:
- display information about N posts starting from M-th ordered by date - the information includes title, abstract and date,
- display one post,
- create new post.
And features with little lower priority:
- edit an old post,
- comments,
- drafts,
- multiple authors.
Note about database
While in SQL-based databases you need to prepare structure of data before interacting with data, MongoDB with schemaless approach do not need it. Data is stored as collections of JSON objects (or trees). Databases and collections are created automatically on first object insertion. Objects are schemaless so in one collection you can have objects describing animals and computer hardware whether it makes sense to you. Objects are mutable so you can change their structure not only within upgrading to new version of software process, but also to keep several versions of schema in the system at once (for e.g. preserving objects from old version of app and handling them as before, giving the option to upgrade to new version) or do it dynamically based on some conditions. Mongo limits object size to 16MB (as of version 1.8.0).
Views
From now on we focus on views directory. The directory structure and names are strongly tied to URLs. /<projects-path>/<app-domain>/views/a/b/c.xml file will be used always when server will be requested for URL http://example.com/a/b/c We need to create following views:
- default - default view which will be used always when URI is http://www.example.com/ - without view name; This view will return list of 10 most recent posts by default and when used with parameters it will show N posts from M-th ordered by creation date; it will also include pagination widget,
- post - returns one post with text, and for logged in user displays interface to edit post,
- register - registers user; it will probably be required once and we will copy this definition from template,
- login - logins user; we will copy it from template,
- logout - log outs user; we will copy it from template,
- addPost - adds new post,
- updatePost - updates existing post
Views are written in the BLSL language. The view template is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<view xmlns="http://asyncode.com/View">
<condition value=""/>
<param name="paramName"/>
<post>
<param name="postParamName"/>
</post>
<set name="variableName"/>
<node name="nodeName"/>
</view>
<view/> is the root element just like <html/> is in HTML documents, <condition/> defines condition which must be fulfilled in order to execute view. There can be many conditions.<post/> is container for defining HTTP POST data handling rules. <param/> defines names and behavior of input data. <set/> sets variable to specified value. <node/> behaves just as <set/> but generates document fragment which is outputted to browser and not reusable within view file anymore (it behaves just like print/echo function from programming languages). <node name="view"/> is special name which is used by ACR.
BLSL tutorial covers tags and ObjectPath language. You may want to read it first but it is not necessity.
Default view
As we said, default view will display ten most recent posts. To achieve just that we can write:
<?xml version="1.0" encoding="UTF-8"?>
<view xmlns="http://asyncode.com/View"
xmlns:mg="http://asyncode.com/Mongo">
<node name="posts" command="mg:find" mg:coll="posts" mg:limit="10" mg:sort="add_date">
{}
</node>
</view>
The xmlns:mg imports MongoDB component (because of optimizations, ACR exports some of its functionality to components) and defines namespace shortcut to it. Mongo component is responsible for database interactions. mg:find is 1 to 1 implementation of Mongo find() function, and means: find objects in posts collection, sort them by add_date and return first 10 of them. The result will be serialized and pushed to the user agent. Depending on requested format, ACR will produce JSON or ObjectML. Default is ObjectML which looks like:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/xslt/index.xsl"?>
<list>
<list name="posts" view="articles">
<object>
<_id>2</_id>
<title>Title</title>
<addtime-date>2010-12-30</addtime-date>
<addtime-time>15:25:34</addtime-time>
</object>
<object>
<_id>1</_id>
<title>Title</title>
<addtime-date>2010-12-29</addtime-date>
<addtime-time>12:01:20</addtime-time>
</object>
...
</list>
</list>
You can consume this data by your own software, but here we want to display it in HTML. AC Frontend helps us with that. We need to add layout node to view:
<?xml version="1.0" encoding="UTF-8"?>
<view xmlns="http://asyncode.com/View"
xmlns:mg="http://asyncode.com/Mongo">
<node name="posts" command="mg:find" mg:coll="posts" mg:limit="5" mg:sort="addtime">
{}
</node>
<set name="layout">
<pageTitle><ml name="default"/></pageTitle>
<container name="body">
<column name="left" width="twothirds">
<widget name="add_post">
<a href="/admin/editPost">New post</a>
</widget>
<widget name="posts" dataSource="posts">
<h3>
<node name="title"/>
</h3>
<div class="info"> <node name="addtime"/></div>
<p>
<node name="abstract"/>
<a href="/post/" class="readMore">
<pars for="href"><node name="_id"/></pars>
read more »
</a>
</p>
</widget>
</column>
</container>
</set>
</view>
AC Frontend (ACF) template engine is written using browser-side XSLT language. It is great optimization because all computations are performed on user computer. ACF introduces new XML format that is meant to replace HTML - the RichML. It merges best techniques of using HTML, CSS and Javascript and wraps them in simple tags. <container/> and <column/> grid tags are taken directly from OOCSS Grids (line and unit, respectively), widths are written as English words rather than OOCSS's sizeXofY (width can be half, third, quoter, fifth, sixth, twothirds and so on), <widget/> is a block with content (grid elements cannot contain nothing else than another grid elements or widgets). Widgets are predefined and default one is HTML container with enhancements.
Widgets
Widgets can consume XML structures defined in ACR by <node/> element. dataSource attribute tells widget where it should take data from. In previous example we pointed posts as data source, thus enabling us to use set of tags of default widget:
- <node name="subtag"/> - takes data from data source's subtag named <subtag>,
- <attr name="attr"/> - takes data from attribute of data source's root element,
- <pars for="attr_name"/> - this is the tricky one; allows to dynamically create tags attribute, in previous example we used it to build href attribute of <a/> element.
Widgets automatically detect datasource structure and handle single objects, lists of objects and object trees. If a list of object is found, parent widget (list widget) is created containing one child widget per list element, if tree mode is set, ACF will crawl the tree and apply template to every object found.
The output of previous example will be W3C-compatible HTML (or XHTML if you like):
<html>
<head>
<title>Posts - My blog</title>
</head>
<body>
<div id="posts" class="widget wiki-list ">
<div class=" widget wiki-item first ">
<h3>Header</h3>
<div class="info"> 2011-04-10 13:17:16.890000 </div>
<p>
<a class="readMore" href="/post/4da191bc62ed970ab4000000">read more »</a>
</p>
</div>
...
</div>
...
</body>
</html>
Pagination
We need to make this view dynamic because it needs to return N posts starting from M-th. We will do it by adding params and using them in mg:find command:
<view xmlns="http://asyncode.com/View"
xmlns:mg="http://asyncode.com/Mongo">
<param name="offset" type="number" default="0"/>
<param name="skip" type="number" default="10"/>
<node name="posts" command="mg:find" mg:coll="posts" mg:limit="{$.limit}" mg:skip="{$.skip}" mg:sort="addtime">
{}
</node>
...
</view>
Now by going to http://localhost:9999/default/5/5 we will see five posts from 5th to 9th.
offset and limit are names of variables defined by a developer. There are no restrictions on naming. <param name="<name>"/> tells system that data for variable of the name <name> should be taken from URI. Order of <param/>'s is meaningful and it refers to order of values in URI. Params are stored in request storage. It is the space where you can add variables at any time. Request storage will live only during execution of view. After that it will be removed permanently.
We can paste value of variable at any XML attribute value or content by writing {$.name}. {} notation is not yet fully ObjectPath, but we working on that.
Pagination can be done in more than one way. Because it is common feature, the appropriate widget is included in ACF. It needs only three values: offset, limit and quantity of all posts.
<view xmlns="http://asyncode.com/View"
xmlns:mg="http://asyncode.com/Mongo">
...
<set name="body" before="layout">
<pageTitle><ml name="default"/></pageTitle>
<container name="body">
<column name="left" type="twothirds">
<widget name="add_post">
<a href="/admin/editPost">New post</a>
</widget>
<widget name="posts" dataSource="posts">
<h3>
<node name="title"/>
</h3>
<div class="info"> <node name="addtime"/></div>
<p><node name="abstract"/> <a href="/post/" class="readMore"><pars for="href"><node name="_id"/></pars>read more »</a></p>
</widget>
<widget name="paginate" type="paginate" offset="{$.skip}" quantity="{$.postsno}" limit="{$.limit}"/>
</column>
</container>
</set>
...
</view>
Add/edit view
Adding and editing is merged into one editPost view because of Mongo's save() functionality which inserts when object does not exist in collection or updates otherwise.
<?xml version="1.0" encoding="UTF-8"?>
<view xmlns="http://asyncode.com/View"
xmlns:mg="http://asyncode.com/Mongo">
<param name="id" default="null"/>
<node name="post" command="mg:find" mg:coll="posts" condition="$.id">
{"_id":objectID($.id)}
</node>
<set name="body">
<container name="body">
<widget name="form" type="form" action="/api/savePost" datasource="post">
<h2>Add text</h2>
<item name="title" type="text" ml="title" required="true"/>
<item name="content" type="textarea" ml="content" required="true"/>
</widget>
</container>
</set>
</view>
We used form widget which is abstraction layer over HTML forms. It is simpler to manage, supports multilingual labels, required fields and so on.
Saving view
<?xml version="1.0" encoding="UTF-8"?>
<view xmlns="http://asyncode.com/View"
xmlns:mg="http://asyncode.com/Mongo"
xmlns:op="http://asyncode.com/Interpreter"
xmlns:h="http://asyncode.com/Headers">
<!-- we are using safeHTML type to handle XSS attacks -->
<post>
<param name="id" type="nonempty" default="null"/>
<param name="title" type="safeHTML"/>
<param name="content" type="safeHTML"/>
</post>
<!-- addtime and modtime should be equal -->
<set name="now" command="op:exec">
now()
</set>
<!-- base for Mongo query object -->
<set name="mgObj" command="mg:save" mg:coll="posts">
{
"title":$.title,
"content":$.content,
"by":$ss.ID,
"modtime":{"$date":$.now},
"addtime":{"$date":$.now}
}
</set>
<!-- we are adding _id property when it is available. If not, nothing happens. -->
<set name="_id" path="$.mgObj" condition="$.id">
objectID($.id)
</set>
<!-- we are passing mgObj to Mongo driver. done has information about execution success. -->
<set name="done" command="mg:save" mg:coll="posts">
$.mgObj
</set>
<set name="redirect" command="h:redirect" h:location="/"/>
</view>
We used here headers component to redirect user to main page after saving post. Data from HTML form is strictly described in the <post/> element. <param/> inside the <post/> has identical behavior to <param/> but works with HTTP POST method.
Further steps
Above cover most basic functionality of blog. You will find much more functionality in Blog app package. Examine the code and read comments. You should be now able to understand reference manuals. They are, along the working examples, the best way to master Asyncode technology.
If the documentation is not enough for you, you can get community or commercial support from Asyncode.
