RikuloRikulo

UXL Overview

UXL (User-interface eXtensible language) is a markup language for describing applications' user interfaces. UXL is a simple variant of XML. It allows you to define user interfaces in a similar manner to authoring HTML and XML pages.

Depending on your preference and your team's skill, you can design UI in Dart, UXL or both.

How it Works

How UXL works

First, you define the user interface in a UXL file, then you can use UXL compiler to compile it into a dart file containing the defined templates.

Installation

Rikulo Gap is not part of Rikulo package. You have to add this to your pubspec.yaml (or create it):

dependencies:
  rikulo_uxl:

Then, If you’re using Dart Editor, select “Pub Install” from the “Tools” menu. If you’re rocking the command line, do:

pub install

For more information, please refer to Pub: Getting Started.

Usage

There are two ways to compile UXL: automatic building with Dart Editor or manual compiling.

Build UXL with Dart Editor

To have Dart Editor taking care of UXL compiling, simply add a build.dart in the root directory of your project with the following content:

import 'package:rikulo_uxl/uc.dart' show build;

void main(List<String> arguments) {
  build(arguments);
}

With this build.dart script, whenever your UXL is modified, it will be re-compiled.

Compile Manually

You can also compile a UXL file manually by running uc.dart (UXL compiler) with command line interface as follows:

dart bin/uc.dart your-uxl-file(s)

The UXL compiler will generate a Dart file for each UXL file you gave.

UXL Templates

A UXL file defines one or multiple templates. A template will be compiled into a Dart function that you can use to instantiate a composite of views repeatedly.

For example, here we define a SignIn template:

<Template name="SignIn" args="rememberMe">
  <Panel layout="type:linear; orient: vertical; spacing: 8"
    profile="location: center center; width: 180; height: 145">
    Username or Email
    <TextBox id="username" value="$rememberMe" />
    Password
    <TextBox id="password"/>
    <Button text="Sign in"/>
  </Panel>
</Template>

UXL compiler will then compile it into a Dart file containing the following function:

List<View> SignIn({View parent, rememberMe}) {
  List<View> _vcr_ = new List();
  ...
  final _v0_ = (_this_ = new Panel())
  ...
  if (parent != null)
    parent.addChild(_v0_);
  _vcr_.add(_v0_);
  ...
  return _vcr_;
}

Then, you can instantiate the views defined in this template whatever you want. For example,

void main() {
  SignIn()[0].addToDocument();
}

Sign in

Data Binding: bind Dart objects

The binding of Dart objects into views are straightforward. As shown in the above example, you can define as many as arguments you want in the template. Then, you can invoke it by passing the right object to it. For example, you can retrieve the user's name from the cookie and then pass it to the template function:

SignIn(rememberMe: getRememberMeFromCookie())[0].addToDocument();

You can also reference to Dart objects in the XML attributes. For example,

<TextBox value="${customer.firstName}, ${customer.lastName}"/>

Notice that if the expression has exactly one ${...}, it will be generated directly (without converting to a string). It is useful if you'd like to specify a non-string value:

<View left="${100}"/> <!-- assign 100 rather "100" to left -->
<Foo user="${currentUser}"/> <!-- you must make sure the types match -->

On the other hand, you have to enforce the string conversion if the type you expect is a string:

<TextBox value="${foo.toString()}"/>
 <!-- toString required if foo isn't a string since TextBox's value expects -->

Notice that it is XML, so you can express multiple lines by simply entering LINEFEED:

    <TextView html="
    <ul>
      <li>Now is ${new Date()}</li>
    </ul>" />
      <!-- auto convert to string since it is not a pure ${..} -->

Command Binding: handle events

You can bind an event to a Dart function with the on.event attribute:

<Button text="Sign in" on.click="signIn"/>

Then, whenever the button is clicked, the function called signIn will be called. The function is called command handler. The signature is as follows:

void signIn(ViewEvent event) {
  //handle the command
}

By default, the command handler is a top-level (aka., global) function. However, for sake of better structure, you can handle multiple commands in a single instance. It can be done easily with the control attribute:

<View control="SignInControl"> <!-- SignInControl is a Dart class -->
  ...
  <Button text="Sign in" on.click="signIn"/>

Then, you can handle commands in an instance of SignInControl (which is created automatically when the view is instantiated):

class SignInControl extends Control {
  void signIn(ViewEvent event) {
    //handle the command, such as altering model and updating UI
  }
}

As shown, SignInControl shall extend from Control.

It is the so-called Controller in the Model-View-Controller (MVC) design pattern.

Embed Dart code into UXL

You can embed Dart code with the dart directive. For example, you can declare the generated dart file as a library or part of a library.

<?dart
library foo;
import "package:rikulo_ui/view.dart";
?>

You can even make a UXL file as a standard-alone application:

<?dart
import "package:rikulo_ui/view.dart";

void main() {
  SignIn()[0].addToDocument();
}
?>

<Template name="SignIn" args="rememberMe">
....

Model-View-Controller (MVC)

Embedding Dart code in a UXL file is convenient. However, for sake of maintenance, it is usually better to separate the code from the view as much as possible. Furthermore, people who write UXL files might not know Dart at all.

To do so, you can apply the so-called Model-View-Controller (MVC) design pattern. For more information, please refer to MVC Overview.

Use templates in templates

Each XML element in a UXL can describe no longer how to instantiate a view but also how to invoke a template. For example,

<Template name="FriendList" args="friends">
  <Template name="Friend" args="friend">
    Name: ${friend.name}
  </Template>
  <Friend friend="$each" forEach="each in friends"/>
</Template>

Notice that a template is actually a Dart function, while a view is an object. UXL compiler will generate Dart differently according to their types.

If the template you are going to use is defined in other UXL file, you can declare them with the template directive:

<? template Friend ?>
<Template name="FriendList" args="friends">
  <Friend friend="$each" forEach="each in friends"/>
</Template>

Define multiple templates in a UXL file

You can define multiple templates in the same UXL files:

<Template name="Foo1">...</Template>
<Template name="Foo2">...</Template>

It will generate two global functions, Foo1 and Foo2.

UXL is basically a XML document, but multiple root elements are allowed. Thus, you can declare multiple templates as shown above.

In additions, you can put one template inside another:

<Template name="Foo1">
  <Template name="Foo2">...</Template>
  ...
</Template>

It will generate one global function, Foo1, and a local function, Foo2. Foo2 is a local variable of Foo1. For more information, please refer to the Template element.