Skip to content
Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
425 lines (310 sloc) 13.4 KB

Aquameta 0.2 - Cheat Sheet

Summary of Aquameta APIs and patterns.

  • command-line access to the database
  • bundles
  • resources
  • widgets
  • database API
  • combining widgets and data
  • local event handling
  • communicating between widgets

This document assumes you know basic HTML, CSS, Javascript, jQuery. Some familiarity with PostgreSQL and Docker will be helpful as well.

1. Accessing the Database

Underneath the hood is a stock PostgreSQL database, which you can access via the command line (and someday a fancy user interface) to create schemas, tables, etc. for use in your application.

open database shell

$ docker exec -it 0f84133a577e psql aquameta
psql (9.6.0)
Type "help" for help.

aquameta=# 

show schemas

aquameta=# \dn
    List of schemas
    Name     |  Owner   
-------------+----------
 bundle      | root
 endpoint    | root
 event       | root
 filesystem  | root
 http_client | root
 ide         | root
 meta        | root
 public      | postgres
 semantics   | root
 widget      | root
(10 rows)

create schema

aquameta=# create schema 'beehive';
CREATE SCHEMA
aquameta=#

set search_path

aquameta=# set search_path=beehive;
SET
aquameta=#

create table

aquameta=# create table some_table (
aquameta(# id serial primary key,
aquameta(# message text,
aquameta(# color text,
aquameta(# number integer);
CREATE TABLE
aquameta=#

You can do a lot with PostgreSQL. Consult the documentation for more information.

2. Bundles

A bundle is a version-controlled collection of rows in the database, similar in function to a git repository. The bundle management interface can be accessed via the browser at /dev, to manage bundles and create new ones, as well as stage and commit changes, and checkout previous versions of a repository.

3. Resources

A resource is a static base page that is served up at the specified path. You can put any HTML you want in a resource, but typically they look like this:

<html>
    <head>
        <title>My Cool Project</title>
        <script src='/system.js'></script>
        <script>
            System.import( '/widget.js' ).then( function( widget ) {
                window.endpoint = new AQ.Database( '/endpoint/0.1', { evented: 'no' } );

                // some commonly used bundles
                AQ.Widget.import( 'org.aquameta.core.ide', 'ide', endpoint );
    
                // import your project's bundle, and specify a namespace alias
                // CUSTOMIZE THIS:
                AQ.Widget.import( 'org.flyingmonkeys.myproject', 'myproj', endpoint );

                // append a base widget to the page, usually called "main".
                // syntax is {bundle_alias}:{widget_name}.
                // CUSTOMIZE THIS:
                $('body').append( widget( 'myproj:main' ) );

                // add the on-screen debugger
                $('body').append( widget( 'ide:debugger3_manager' ) );

            }).catch( function( e ) {
                console.log( 'System.js error:', e );
            });
         </script>
    </head>
    <body></body>
</html>

4. Widgets

Aquameta user interfaces are made up of widgets. A widget is a row in the database which contains fields of HTML, CSS and Javascript.

Available Variables

The following variables are available by default, in a widget's HTML, CSS and Javascript:

  • $, the jQuery library
  • name, the name of the widget
  • id, the DOM id of the widget
  • endpoint, the AQ.Database object that can be used to access the REST interface
  • Any arguments passed into the widget

HTML and CSS

Aquameta uses the doT.js template language. You can do a lot with doT.js, but mostly we just use it to print variables:

In the widget's HTML:

<div id="{{= id }}" class="{{= name }}">
    <p>Hello, I am a widget who was passed a variable called "color" whose value is  {{= color }}</p>
</div>

In the widget's CSS, we use {{= name }} to reference the name of the widget and apply CSS rules to it:

/* apply css to the base element of the widget */
.{{= name }} {
    background-color: white;
    color: black;
}

/* apply css to all p children under this widget */
.{{= name }} p {
    text-decoration: underline;
}

Javascript

A widget's Javascript is executed after the widget's HTML and CSS are inserted into the page. From the Javascript, you can insert data into the widget's HTML, bind DOM events to handler functions, access the database, and more.

Typically the fist line of a widget sets up the w variable, a jQuery object that references this widget:

var w = $("#"+id);

// my widget code here

Simple button click handler

html:

<div id="{{= id }}" class="{{= name }}">
    <button>click me</button>
</div>

javascript:

var w = $("#"+id);

w.find('button').click(function() {
    alert ('You clicked the button');
});

Set contents of an element

html:

<div id="{{= id }}" class="{{= name }}">
    <h3 class='person_name'></h3>
</div>

javascript:

var w = $("#"+id);

w.find('div.person_name').html("John Smith");

Calling other widgets via widget(name, args)

Widgets are loaded with a call to the widget() function, which returns an HTML fragment suitable for inserting into the page. In the call to widget, the name argument expects a string with syntax {bundle_alias}:{widget_name}; the args argument is a Javascript object containing any arguments passed into the widget.

For example, if we have a widget named "colorpicker" in a bundle imported with AQ.Widget.import( 'org.fancypants.myproject', 'mp', endpoint ), which expects an argument called start_color, the widget would be put on the screen as follows:

w.append(widget('mp:colorpicker', { start_color: '#ff0000' }));

5. Database API

AQ.Database(url)

Every widget has a variable called endpoint that can be used to access the database. The database object is instantiated in the Base HTML page, via the call to:

window.endpoint = new AQ.Database( '/endpoint/0.1', { evented: 'no' } );

Requesting a AQ.Rowset via .rows()

Here's a simple call to request all rows in the table beehive.customers. It returns a Promise for a AQ.Rowset as described in the fetch API.

// get a promise for all rows in the beehive.customer table
var customers = endpoint.schema('beehive').table('customer').rows();

This code immediately assigns customers to a Rowset Promise, and then asyncronously fires a request to the server to retrieve the actual customers data.

Requesting a Single AQ.Row via AQ.Relation.row(pk_name, pk_value)

var customers = endpoint.schema('beehive').table('customer').row('id', 12345);

Requesting Related Rows via AQ.Row.related_rows()

Often times we have a row, and need to jump across a foreign-key to get related rows that foreign-key to or from the first row. The Row.related_rows() method makes this possible:

AQ.Row.related_rows(local_key_column, related_table, related_key_column [, modifiers ])

For example, let's say we have two tables, beehive.customer and beehive.order. Every beehive.order row foreign-keys to the the customer table's id field via the order.customer_id field.

Once we have a customer object, we can retrieve all orders that foreign-key to it via:

var orders = customer.related_rows('id','beehive.order','customer_id');

Now, orders is assigned a Promise to an AQ.Rowset containing all orders that foreign-key to this customer.

Filtering Results Using .rows(Modifiers)

Modifiers provide a simple mechanisms for filtering result data via where, order, limit, and offset.

Modifiers never change the structure of results, they just sort and filter the results. For more advanced server-side data manipulation, create a view.

var customers = endpoint.schema('beehive').table('customer').rows({
    // filter out results that do not match the supplied where-clause(s)
    where: [{
        // the name of the column to filter on
        name: 'name',
        // the operation to use
        op: 'like'
        // the value to check against
        value: '%john%'
        // this results in a SQL statement "WHERE name like '%john%'"
    }],
    // sort the results
    order_by: {
        // column to sort on
        column: 'name',
        // direction to sort, either 'asc' or 'desc'
        direction: 'desc'
        // result: ORDER BY name desc
    },
    // only return a max of 20 results
    limit: 20,
    // skip the first 10 results
    offset: 10
});

Handling Promise Resolution via .then()

When the server returns the customer rows, we can attach an event handler via Promise.then():

customers.then(function( customers ){
    console.log('We got customers: ', customer);
}).catch(function( e ){
    console.error("Error loading customers: ", e);
});

Iterating Through an AQ.Rowset via .forEach()

We can iterate through a customers Rowset, and call a function on each row in the Rowset. The function will be passed an AQ.Row object:

// iterate through customers
customers.forEach(function( customer ) {
    // customer is a AQ.Row object
    console.log(customer);
})

Accessing Row data via AQ.Row.get(), AQ.Row.set() and AQ.Row.update()

Each AQ.Row object has functions for getting, setting and saving data:

customers.forEach(function( customer ) {
    // get the customer address
    var address = customer.get('address');

    // set the customer name
    customer.set('name', 'Sally Smith');

    // save the customer to the database
    customer.update().then(function(customer) {
        console.log('Customer saved');
    }).catch(function(e) {
        console.log('Customer update failed: ', e);
    });
})

6. Combining Widgets and Data via widget.sync()

Often times when we have a AQ.Rowset, we want to put a widget on the screen for each row in the rowset. We can of course iterate through the rowset with a .forEach() call, but widget.sync is a lot cooler.

widget.sync takes a Rowset, iterates through each row, calls the specified widget_function, passing it the row, and appends what the function returns to the supplied container.

Syntax: widget.sync(Rowset, $container, widget_function)

html:

<div id="{{= id }}" class="{{= name }}">

    <h3>All Customers</h3>

    <div class='customers_container'>
         <!-- customer widgets will go here -->
    </div>
</div>

javascript:

var customers = endpoint.schema('beehive').table('customer').rows();

widget.sync(customers, w.find('.customers_container'), function(customer) {
    return widget('bh:customer_summary', { customer: customer });
});

7. Communication Between Widgets

Widgets communicate with each other using DOM events via jQuery's trigger() and bind(). Trigger fires a named event that bubbles up the DOM tree, just like typical DOM events. Bind listens on a particular DOM element for events matching a particular name. When it receives one, it fires the specified function, passing it any arguments that the trigger was called with.

For example, let's say we want to create a app-wide general-purpose widget that flashes a message on the screen to the user. With it, any other widget can trigger an event to alert the user of whatever message.

Define the Event Name and Args

First let's give the event a name and expected arguments. We will name the event alert_message, and say that it expects an argument object like { message: "Hi Mom!" }.

Event Listener Widget

Let's first create the widget that listens for alert_message events and shows them to the user.

Bind only recieves events that are fired on its descendent elements. As such, we typically want to bind to some parent widget, a widget that is a parent to all widgets that will be calling this listener. Often times the root widget of the app works great, since all widgets in the app are its descendents.

var w = $('#'+id);

// find the ancestor widget .main, and bind the alert_hanler to it
w.closest('.main').bind('alert_message', function(e,o) {
    // o is whatever is passed into the second argument of .trigger()
    var message = o.message;

    // do the alert
    alert(message);
});

Now, anywhere in our app, we can trigger an event with this name and arguments, and the listener will recieve and handle it. Whenever .main recieves a alert_message event, the handler will fire and show the message passed in.

Widget that Triggers an Event for the Listener

Let's give it a spin:

html:

<div id="{{= id }}" class="{{= name }}">
    <button class='compliment'>Compliment user</button>
</div>

javascript:

var w = $("#"+id);

w.find('button.compliment').click(function() {
    w.trigger('alert_message', {
        message: 'My, you look very lovely today!'
    });
});

Clicking the "Compliment user" button calls the click handler function, which triggers the event, and presto.

Conclusion

These are the basics of how to build apps with Aquameta. Thank you. The end.

You can’t perform that action at this time.