Coala is an asynchronous data exchanging library designed to run on the web. It uses XMLHTTP to communicate data between the client and the server over asynchronous HTTP. As of Rabbit compilation 11188, Rabbit provides a server-side dispatcher for Coala. Rabbit utilizes type-safety to make sure Coala arguments are dispatched safely. The client-side of Coala is independent of other libraries, if the programmer desires to develop a server-side dispatcher (a simple one is trivial to develop).
Origins
The engine was established on November 19, 2006, by dionyziz, in an attempt to create a generalized AJAX system. It was essentially derived from many ideas collected during the development and use of the incomplete and non-XML-compatible AJAX Engine of BlogCube, although no code has been ported over and it has been developed from scratch, leading to much clearer and more compact code. The name was inspired by the Koala species and is intentionally misspelled to refer to the engine -- it should not be confused with the Coala game. The Coala engine has only been used live on Zino running Excalibur, although by an adequate number of users to consider it a stable library.Support
- It is a generalized AJAX library; it doesn't require the programmer to write code such as XMLHTTP handling over and over again.
- The client-side of Coala is independent.
- It is cross-browser compatible.
- It is minuscule.
- It is fast; the server-side code introduces only tiny overhead against direct calls.
- It is relatively easy to use, compared to other similar libraries.
- It allows passing client-side objects (such as lambda functions) transparently through the server and back, using pointers to avoid data transfer overhead.
- It encourages good programming practices, by providing the call arguments referentially transparently.
- It is XML-strict and XHTML-strict.
- It is type-safe.
Criticism
- The server-side part of Coala is dependent on Rabbit and cannot run independently.
- It encourages non-generalized code, by allowing units to send back Javascript to-be-executed.
- It does not support any Reverse AJAX techniques.
- It does not separate cacheable from non-cacheable requests.
- No routing is possible for units.
- It is not possible to pass arrays or objects that are readable server-side.
Units
Units are structural modules that are externally callable by the client. Care should be taken to validate the given arguments to a unit, and even the fact that the unit call was legitimate, for example by checking that the currently logged in user has permission to access the given unit, since unit calls can be fired by external users freely.Unit Names
Each unit must have a unique name, which can consist of any combination of lower-case Latin characters and numbers. The slash character can be used within a name to group similar units in a folder- and subfolder-like manner. For example, similar units related to, say, user operations can be grouped under the name "user/". So if, for instance, there are two units, one for logging in, and one for logging out, they could be named "user/logon" and "user/logoff" respectively. The starting letter of each unit name, as well as each letter following the slash character must always be a lower-case Latin character and cannot be a number. Unit names must also be unique when the slashes are removed from their names. Hence, it is not possible to have two units named "user/logon" and "userlogon" respectively, as this introduces a naming conflict when the slashes are removed.Unit Types
Each unit has a type, which can be either of "Warm" and "Cold". If the unit causes changes to take place on the server-side, and affects the state of your website by updating database information, it should be declared as "Warm". If it is used for data fetching, it should be declared as "Cold". It is important to separate Warm and Cold units, because misjudging a Warm unit as Cold can lead to XSS problems. The guidelines of the HTTP Specification should be used to distinguish Warm and Cold types.Unit Arguments
Arguments are a way to pass data from the client to the server, when executing a unit. Each unit can take zero or more arguments, each of which has a specific name and a position. Unit argument names must be valid PHP and Javascript variable names. Unit argument are passed type-safely, so when units are called their arguments must have the same names as in their definitions and the order does not matter. Optional arguments are possible as in all type-safe functions.Unit Filenames
Each unit is stored in a file separately; more than one units cannot be stored in the same file. Units are located under the "units" folder of your project root. The name of the file in which a unit is stored is constructed as follows:- The file starts with "units/"; i.e. all units are in the units folder
- The name of the unit follows
- If the unit is warm, ".do" follows
- All unit filenames end in ".php"
So, for example, if your unit name were "user/logon" and it was a warm unit, the name of the file where it would be stored would have been "units/user/logon.do.php". On the other hand, if your unit name were "messages/get/latest" and it was a cold unit, the name of the file would have been "units/messages/get/latest.php". Keep in mind that, even though the filename of a warm and cold unit with the same name is different, it is not possible to have two distinct units with the same name and a different type; each name is associated with one unit and one type only!
Unit Function Names
Each unit has a so-called unit function name, which is used server-side to identify the execution block of the unit. This function name is unique per-unit and can be obtained by the unit name easily:- Start by taking the unit name
- Capitalize the first letter and each letter after a slash character
- Remove all slash characters
- Add the word "Unit" at the beginning
So, for instance, the unit function name of a unit named "messages/get/latest" would be UnitMessagesGetLatest.
Importing to the Client
First off, you need to import the file coala.js to the client:<?php $page->AttachScript( 'javascript', 'js/coala.js' ); ?>
This will load the necessary library to the client.
Keep in mind that you have to leave the "Coala" JavaScript variable undefined prior to this call, and not change it afterwards, as it is reserved by the Coala system.
Defining Units
Units are defined by creating their unit file. The file can contain PHP code, which you can use to perform any server-side operation you like. Unit files, like everything else in Rabbit, can be masked. No bulk PHP code or direct output outside functions must be present within the unit file. Hence, no library imports, globalizations, or initializations can be done in bulk. The one and only thing defined within a unit file must be a PHP function whose name must be the unit function name. The arguments of this function define the unit arguments. Keep in mind that only argument names (and not the order) are important when defining the argument list of a unit, like every other type-safe function, unlike usual PHP functions where only the order is significant.Here's an example definition of the cold unit messages/get/latest, in the file units/messages/get/latest.php:
<?php // no code or output is allowed above this line function UnitMessagesGetLatest( tInteger $offset ) { $offset = $offset->Get(); // verify that the user has permission to call this unit // verify that the argument $offset is legitimate (e.g. make sure it does not exceed the number of available pages) // do something } // no code or output is allowed below this line ?>
Returning
Unit functions should not return a value. If a value is returned, it will simply be ignored by Coala, and it will not be passed to the client in any way. An empty return statement can be used to stop execution of the unit. Statements such as die() or exit() must not be present within the unit, as they do not allow garbage collection by the framework. If necessary for debugging, instead of die(), use an echo statement followed by a return.It is possible to have a unit output certain data using direct output. This data should be valid JavaScript code, and will be executed by the client as soon as it is received. This can be used, for example, to modify the document DOM according to data changes. Keep in mind that JavaScript rules apply more strictly to Coala output; hence, even though it is possible to skip the ";" at the end of a JavaScript line, this is not possible when using Coala, so be careful. Other than that, providing your code is valid JavaScript, no further restrictions apply. You can use both double and single quotes, regular expressions, and so forth without problems.
Access to DOM objects and global objects in the scope in which the Coala library was imported is available. So, for example, you can use the "Coala" object (as illustrated later in this document) and the browser "document" and "window" objects freely.
Here is an example that uses the JavaScript outputing functionality of Coala to modify the DOM and append an image at the end of document body:
<?php function UnitImageGet( tInteger $imageid ) { $imageid = $imageid->Get(); if ( $imageid < 1 ) { return; } // import some library that defines the function Image_GetSource() at this point $imagesrc = Image_GetSource( $imageid ); ?>var myimg = document.createElement( 'img' ); myimg.src = '<?php echo $imagesrc; // providing imagesrc does not contain backslash and single quotes ?>'; myimg.alt = myimg.title = ''; document.body.appendChild( myimg );<?php } ?>
Calling Units
Units can be called using the asynchronous functions Coala.Cold() and Coala.Warm(). Cold units must be called using Coala.Cold() and Warm units must be called using Coala.Warm(), or they will not work. Both functions use the same syntax:<script> Coala.Cold( unitname, parameters ); Coala.Warm( unitname, parameters ); </script>
None of the two arguments are optional. Unitname specifies the name of the unit that you want to call, and parameters specifies a list of parameters passed to the unit. Parameters is specified as a JavaScript dictionary, mapping from argument names to argument values and its count must match the count of the arguments accepted by the unit. The arguments do not need to specified in the same order they were defined for the unit, but care should be taken for the argument names to match case with those defined for the unit. Optional arguments can be omitted. If the unit takes no arguments, the parameters argument should be an empty JavaScript object.
Argument values passed can be of scalar JavaScript types (i.e. number, string, or boolean). These arguments will be converted to the relevant PHP types using type-safety rules. JavaScript objects, arrays, functions, dates, regular expressions and error objects can be passed, but they are handled differently by Coala, as described further below.
Let us illustrate unit calling with an example by defining some units and then attempting to call them.
<?php function UnitUserLogon( tString $username, tString $authtoken ) { $username = $username->Get(); $authtoken = $authtoken->Get(); if ( !preg_match( '#[a-zA-Z0-9]{3,}#', $username ) ) { return; // check should be done by the client } if ( !strlen( $authtoken ) == 32 ) { return; // invalid authtoken passed } // perform logon check and return the result using JS $checklogon = User_CheckLogon( $username , $authtoken ); if ( $checklogon ) { ?>LogonSuccessful();<?php return; } ?>LogonFailed();<?php } ?>
This Warm unit does a simple logon check and calls the Javascript functions LogonSuccessful() or LogonFailed() based on success/failure.
<?php function UnitMessagesGetLatest( tInteger $offset , tInteger $limit ) { $offset = $offset->Get(); $limit = $limit->Get(); if ( $offset < 0 || $limit < 1 ) { return; } // fetch the messages and return them to the client using JS $messages = Messages_Get( $offset , $limit ); ?>GotMessages( <?php echo json_encode( $messages ); ?> )<?php } ?>
This Cold unit fetches some messages, and calls the Javascript function GotMessages() passing their structure to them.
<?php function UnitUserLogout() { User_LogoutCurrentUser(); } ?>
This Warm unit logs out the current user.
These units could be called like so:
<script>
Coala.Warm( 'user/logon', {
'username' : 'dionyziz' ,
'authtoken' : "d41d8cd98f00b204e9800998ecf8427e"
} );
Coala.Cold( 'messages/get/latest', {
'offset' : 5 ,
'limit' : 20
} );
Coala.Warm( 'user/logout', {} );
</script>
Grouping Calls
Consequent calls to Coala.Warm and Coala.Cold are grouped together by Coala, for improving speed. Hence, it is worth keeping in mind to make more calls consequently rather than sparingly, to optimize our program. For example, instead of making 3 different calls in 5 seconds' interval, it is preferable to send all calls together after 10 seconds, so that they are grouped.You don't need to do something special to group calls together, simply make sure they are called the one after the other (for example all of them within the same function -- calls do not have to be directly consequent), and you can be assured that they will be grouped by Coala automatically for you.
As Coala uses asynchronous calls to deliver data, calls to different units may not be executed in-order. For example, if you call unit A followed by unit B, this might lead to unit B executing before unit A. Or it might as well mean that either unit could not be executed at all, if there are heavy network problems. It is up to you to handle network failures and calls in different order than expected.
Grouped calls, however, are an exception to this rule, as they are executed in the pre-specified order and are atomic, meaning that they could either all fail or succeed together, which is one more reason why they should be preferred.
Passing Objects
It is possible to specify a parameter value to be of non-scalar JavaScript type. These include types object, array, function, date, regular expression, or error object. Coala uses a special mechanism to pass these types. Specifically, the actual content of these types is not transferred across the network, but a usable pointer to the content is transferred instead. Hence, they cannot be used to extract part of their values from within PHP within a unit.Then can, however, be used in the JavaScript code outputted by the unit, simply by echoing the PHP variable.
This behavior allows generalizing units by providing pointers to lambda functions, and should be preferred to directly outputting specific JavaScript code, unless the Unit is not expected to be reused, as callbacks can be used.
These variables should be type-safe declared as tCoalaPointer. tCoalaPointer variables cannot have ->Get() used upon them, since they cannot be converted to a scalar or non-scalar PHP type. You can, however, check if a valid object is stored on the client-side using the ->Exists() boolean function, and echo the variable itself in order to pass the reference back to the client.
Let's see how this works by improving our previous "user/logon" unit to use callbacks.
<?php function UnitUserLogon( tString $username, tString $authtoken , tCoalaPointer $onsuccess, tCoalaPointer $onfailure ) { // notice that we only apply ->Get() on tString, // and not on tCoalaPointer variables $username = $username->Get(); $authtoken = $authtoken->Get(); if ( !preg_match( '#[a-zA-Z0-9]{3,}#' , $username ) ) { return; // check should be done by the client } if ( !strlen( $authtoken ) == 32 ) { return; // invalid authtoken passed } // perform logon check and return the result using JS $checklogon = User_CheckLogon( $username , $authtoken ); if ( $checklogon ) { if ( $onsuccess->Exists() ) // if this points to a valid JS object echo $onsuccess; // echo the reference back to the client ?>();<?php // we are calling the function pointed-to by $onsuccess } return; } echo $onfailure; ?>();<?php // we are calling the function pointed-to by $onfailure } ?>
As you can see, now the functions called on success and on failure are not prespecified, but rather decided by the caller. Let's see how we could call a unit like this:
<script>
function login_failed() {
alert( "Login failed" );
}
function login_succeeded() {
alert( "Login succeeded" );
}
Coala.Warm( 'user/logon', {
'username' : 'dionyziz' ,
'authtoken' : "d41d8cd98f00b204e9800998ecf8427e" ,
'onsuccess' : login_succeeded ,
'onfailure' : login_failed
} );
</script>
This way, different calls to the same unit can use different callback functions.
You could even use lambda-style functions, if you prefer:
<script>
Coala.Warm( 'user/logon', {
'username' : 'dionyziz' ,
'authtoken' : "d41d8cd98f00b204e9800998ecf8427e" ,
'onsuccess' : function () {
alert( "Login failed!" );
},
'onfailure' : function () {
alert( "Login succeeded!" );
}
} );
</script>
In a similar sense, you can pass objects, regular expressions, error objects, arrays, and dates. Just keep in mind that you cannot extract any of their values from within PHP itself, but all you can do is pass it back to the client.
Epilogue
Let's hope that this page makes it easier for you to use Coala. As it is a new library, suggestions for improvement are always welcome. There are some plans for new functionalities in the future, which are listed in the TODO section below.Enjoy!
TODO
Document these:- ->Exists() on tCoalaPointers.
- Manual failure handling.
Here are some plans for improving Coala in the future:
- Reverse AJAX and callbacks registering
- Allow cacheable units.