Demo project

After downloading the package, and starting Demo, the application is accessible with typing or just in the Browser:

Demo application

The Application Builder is accessible with typing in the Browser:

Application builder of demo project

The application is derived from the open-source Chinook database. It is an invoicing example with music tracks as invoiced products, customers, albums, etc. Basically, the application creates customer details, product details, and raises invoices for customers. That’s it. Cool little application, though.

As mentioned, the demo application is derived from it. It is not a one-to-one mapping of all database fields and relations. We need to start somewhere, right?

Demo database

To better understand the actual database, here is the original Chinook relational database diagram:

Chinook Database

Why is the above diagram important? Because it is demonstrating the table’s Primary and Foreign Keys, which are essentially table Lookup fields used within More about that later.

So now that we know what the Demo application is all about, we can dive into more details. First, what can we actually expect from

What to expect?

Low code is what everyone talks about. Below is what provides with no coding needed at all, at least not for this Demo. The code does exist; it is there. Otherwise, the application would not work at all. However, this only means that no coding is required by us to build similar applications. Because the code is there and available to us, this also means we can adapt the existing code for our requirements. Mostly, it is a copy/paste from this Demo, with some minor changes for the application we are building. This is important to understand. provides many bells and whistles or so-called batteries out of the box. However, is not an “Out of the Box Application”! It is a framework, so we build applications within the constraints of the framework.

Just like Django’s MVC. When building applications with Django, we operate within similar constraints. Both frameworks are flexible enough for the job at hand. The main difference is the task or the application. Django could build the application, but it would be overkill, since is way more specialized in doing so. is a specialized framework. It is the right tool for the right job. Let’s see what we can expect from it.

Data Grid(s)

The Demo Invoices data grid is called a Journal. In Accounting, these are General Ledger tables. In databases, this is a Master Table. This grid is presented automatically with pagination at the bottom and can contain a History button, refresh button, and Filters button. These are the default options and can be turned on/off. If a non-default button is needed, it can be added with code, though.

Did I mention the search for any field and sorting for any field? It’s automated.

What is also done by with no code is a summary for any numerical field or the number of records for other fields.

As seen, the Invoices data grid is presented as a tab. As we open more grids, more tabs will appear here automatically. Tab captions can be changed with code. What we see initially is the no-code default.

General Ledger tables

More Data Grid(s)

The Demo Invoices data has a Details grid. These are the Invoice items details. They are created automatically and can contain summary fields and sorting. The “Total” field visible is a code! What is not visible is editing directly in the grid!

There can be any number of details data grids. Now, that is actually pretty impressive. Any number, huh? We’ll get there.

Like the above grid, each Details grid is presented in tabbed format. The more Details added to a journal, the more tabs we see here.

Invoices items tables

Any questions?

So now that we’ve covered the first thing we see on the Demo application, any questions?

All of the above is driven by the Builder GUI. We might be saying it’s no big deal, but it actually is a big deal! Because to code this from the beginning, like with Django, one would need years and years of coding experience to cover all possible scenarios. Or a team of dedicated developers.

I am ignoring the Reports and Analytics for now. Only concentrating on no-code or low-code. The Reports and Analytics require some coding experience. Not much, but still needed.

Ok, how do I start?

If no questions (cough, cough), I think we should first start with building some tables. Since we are doing Invoices, lets start with this. It is a sort of top-to-bottom approach!


For an invoice, we need a Customer, some Product and somewhere to store invoiced data, like a Journal. To be fair, Journal is just a name. We do not really need to stick to the Demo application naming! Rename anything to whatever floats your boat!

Editing Journal

We can rename the Journals caption to anything, since this is just a Caption. However, the Name can be used in the code somewhere, so it is always advisable to use the “Find” built-in function to search where some Name is used.

On above screenshot we see the “Deleted Flag” and “Record ID”. This is just a bit of built-in functionality. When we create a new table in the Journals group, these two records will be created automatically. We can think of this as a table template.

The Master field field seen above is also a feature. In the Demo application Invoices table, it is used to identify which item belongs to which Invoice.


Master fields are not real fields in the database, they get populated when we select a value in the Lookup field.

Master fields are virtual fields in that get populated based on the value selected in the lookup field. They are not actual columns in the database table, but rather a way to display related information from another table in the UI. When a value is selected in the lookup field, the master field is automatically updated with the corresponding value from the related table. This allows for easy navigation and visualization of related data in the UI.

This is actually very powerful feature in my books, because it quickly identifies stuff. Which stuff you might ask? Anything built as a Lookup fields. By setting the Master Field to Customer, we can easily identify all data related to the Customers table that we are pulling information from.

I would leave it here for now, but just note that the Master field can also be achieved with code for imported tables. Usually, imported tables are from other systems like MSSQL or MySQL, and are not created by In this case, we can add the table as a detail to only one master. With the Master field feature in, we can do much more. We will get to this later.

Only Invoices, Invoices Items, and Tracks are using the “Master field” in Demo application.

The Demo Invoices table:

Invoices table

We see that Invoices table is referencing a lot from the Customers table! This is where we look at the Chinook database, we see the relation Customers - Invoices!

This means we can create the table but with no Customers one, we can’t really achieve what is needed for Invoices to function correctly.

With no Customers table our Invoices table would look like this:

Invoices table with no Customers

It is exactly the same thing, same as above one would think. Not really. It would not display any data because would assume that all of Customers data is coming from one, and only one table, which is Invoices table. It is also true that all fields would have the Integer Type, which is not quite right. It is presented here just as an example and it is not the right way of doing it.

However, from the diagram, the Invoices is pulling some data from Customers and “Invoice Items” table. That is the Holy Grail of any application building. Pulling data from here and there, and showing the result with no code, or low code, is exactly what does. We call that a Lookup fields.

As seen, on the right hand side checked options are “Visible”, “Soft Delete”, “History” and “Edit lock”. Refer to Item Editor dialog for more info.

Also, worth mentioning is that Invoices do contain a tiny code for calculating “Tax” and “Total” column. It also utilises feature to alert the User with a custom message. It does that on Editing only.

Here we touch the View/Edit option on builder.html. When we click on Journals/Invoices, there is a “View Form” option on the right hand side.

Invoices table view

Here we control the layout, sorting and summary fields, as well as fields visibility on the Demo data grid. On the Form tab, we see a number of other options, but most importantly the “View detail” option, with selected “Invoice table”. We can select as many Details as we like, providing they exist. If no detail is selected, there would be nothing to display. Somehow counter-intuitive on the first instance, however we are controlling the visibility with this option, not more then that.

Invoices table view form

Similar for Edit option. Exclude or select for editing whatever is necessary, as well as details needed for editing. While on it, we can also control how to present the edit form in the sense of tabs or bands. This is done on “Plus” icon on the left hand side. I would encourage to play with this options, it is quite subjective what and how something should be presented.

Invoices table edit form


Back to Customers, if we visited Tutorial. Part 1. First project in the Doc’s, I hope it is pretty much clear how to create a simple CRM. Hence, Demo application has Customers table as well, which is consumed by the Invoices table.

Here is how Customers table looks like:

Customers table

It is a simple table with no lookups to some other tables, like Invoices for example. Because it is derived from the Chinook database, we can see that Demo is missing the Employees table, so the SupportRepid is not used anywhere. That is fine. No harm done. We might argue that Customers table can be split in more tables, like Country, State or similar. While on it, please see Tutorial. Part 2. File and image fields for adding Image field. We did touch base with the View/Edit option on Invoices, no need for repeating.

Now that we have two tables in place, we can set the Lookup fields in Invoices in a similar fashion as in Tutorial. Part 1. First project.

We did not touch the third table yet, which is the “Invoice items”. This is the part of Tutorial. Part 3. Details

Invoice items

To automatically add some details to some other table, with no code what so ever, we need to use Details feature.

Why would we do that? Well, we could opt for coding. It is available feature and used mostly for Imported tables, which do not contain additional fields is using.

However, Demo application is demonstrating how is functioning, so why not using it.

Basically, since the Invoice Table is a detail of the master table Invoices, it has the master_rec_id field that stores a reference to an invoice. As a master record, we can show an invoice that contains the current item. Clear as mud?

The Details Group differs from other default tables slightly:

Detail Group

See the additional fields? The fields in question are master_rec_id and master_id. This fields do not exist in any legacy systems. None of the applications out there are using it. However, let’s not be afraid of this feature. As mentioned, we can and we will achieve the functionality with a bit of code for legacy systems without this fields.

In addition, the option “Visible” is unchecked. It makes no sense in setting the Details as visible, since we edit/view them within the Invoices only. It is possible to set it as visible, though.

Just like before with Invoices table, but not with Customers, the “Invoice Table” is referencing two tables, “Tracks” and “Customers”.

Invoice Items

If below table screenshot was the “blank” Invoice Items table, with no Lookup fields to the above two tables, it would not show anything. The reason why we using “Customers” table and not the “Invoices”, InvoiceId from Chinook database diagram, is the presentation. It is more nicely presented with the customer last name on the screen, as compared to with some meaningless number. In this case it is InvoiceId (integer), as on the diagram, which ultimately identifies the Customer by the CustomerID. Similar with Tracks, we are using it to do the Lookup on a completely different tables, namely “Albums” and “Artists”.

Invoice Items with no lookups

Same mantra, we look at the database diagram. We “dive” deep from “Invoice Items” to “Tracks” to “Albums” and finally to “Artists”.

We are using the power feature - lookups, as much as we can to minimise coding. Just imagine the SQL needed to “join” the three tables! To provide the exact SQL, here is what generates automatically as the last SQL query when we open Invoices:


There we go! We can easily copy/paste the above SQL query into some utility to browse the data in demo.sqlite database. Just imagine coding this type of query for all possible combinations of tables. Btw, this SQL extract is possible by changing the source code.

Now we execute Tutorial. Part 3. Details

How did we go?

So, did we manage to get the Invoices with details up? It is quite common for the first timers to miss the “Details” button on the right hand side of Invoices in builder.html.

On the CRM example, the Details is a “to-do-list”. If all went ok, we should have a project page similar to Demo.

Click on!

With the expectations and basics covered, we can double click on any Invoices row. If all good, we see one invoice with the invoice items included.

Edit Invoice

If nothing was touched or changed, this is how it looks like. Job done. All created automatically, no code yet! Except for “Tax” and “Total”.

See how almost everything related to Customer is greyed out? This is because of the Master field! Only one field on the Invoices table is not using “Master field” and this is a Customer field. Hence, only that field won’t be greyed out, because we are using it to define all other fields on the Form. For sure we are not updating the “Invoice Date” from anywhere, it is defined with a little code. Same with “Tax”, “SubTotal” and “Total”.

Your 1st task!

As mentioned, we can edit anything directly in the data grid! The changes will be picked immediately and saved in the database.

Your first task is to find the option which enables editing in the data grid.

It looks like this:

Edit Invoice

And why not reshuffling the Invoices edit form a bit?

To look similar to this:

Edit Invoice

Very good, we are getting there!

I am not sure if more info is needed about the data grid and forms layout. Just note that the Menu is created from the builder.html Groups layout, so whatever is showing here first, it will be shown as first on the Menu.

If we quickly want to change the application starting view to ie Customers, we just change the Groups order.

Maybe we can go now through a code.

A little code

Before diving in to a code, remember the ACE Code Editor shortcuts!

If one is a beginner with any sort of coding, this might be a bit daunting. However, I can assure you, the developers out there are using copy/paste just like anyone else. So, with a bit of persistence, one can reuse the code from Demo application and get impressive results. This is particularly true for Dashboards, which is a flag feature. We included more then 15-20 graphs for some applications, with a minimal change for each. For example, just changing “pie” to “bar” changes the graph appearance.

The official documentation has a heaps of code applicable to Demo application. Here will try to explain where exactly the code is used and a bit about why. Again, we touching only the additional code applicable for Demo, the default one which comes with a blank and empty project is not discussed.


In Invoices, it is quite obvious that some calculations are happening “on the fly”. At the moment, v5 has no feature to calculate fields automatically, driven by visual application builder.html. The new major version might include this option. This might be a put off for some users or a “would be” developers and the reasoning is that major players, like PowerApps, have that. Sure, they also have a bottomless financing, if you follow my drift.

Here is the tiny code for Demo application version 1.5.30. The code can be found on “Client module”, after selecting Invoices:

function on_field_get_text(field) {
    if (field.field_name === 'customer' && field.value) {
        return field.owner.firstname.lookup_text + ' ' + field.lookup_text;

function on_field_get_html(field) {
    if (field.field_name === 'total') {
        if (field.value > 10) {
            return '<strong>' + field.display_text + '</strong>';

function on_field_changed(field, lookup_item) {
    var item = field.owner,
    if (field.field_name === 'taxrate') {
        rec = item.invoice_table.rec_no;
        try {
            item.invoice_table.each(function(t) {
        finally {
            item.invoice_table.rec_no = rec;

function on_detail_changed(item, detail) {
    var fields = [
        {"total": "total"},
        {"tax": "tax"},
        {"subtotal": "amount"}
    item.calc_summary(detail, fields);

function on_before_post(item) {
    var rec = item.invoice_table.rec_no;
    try {
        item.invoice_table.each(function(t) {
            t.customer.value = item.customer.value;
    finally {
        item.invoice_table.rec_no = rec;

As seen, the Client module contains five JavaScript functions. First two functions deal with text formatting. The on_detail_changed is using built in function calc_summary.

Lastly, one of the most important function and the most commonly used is:

function on_field_changed(field, lookup_item) {     <- 1.
    var item = field.owner,                         <- 2.
    if (field.field_name === 'taxrate') {           <- 3.
        rec = item.invoice_table.rec_no;
        item.invoice_table.disable_controls();      <- 4.
        try {                                       <- 5.
            item.invoice_table.each(function(t) {
        finally {                                   <- 6.
            item.invoice_table.rec_no = rec;
            item.invoice_table.enable_controls();   <- 7.
    }                                               <- 8.

Understanding this function is quite important. It provides a mechanism to control what happens when some input on the application is changed.


1. In this case we are looking to “monitor” the “taxrate” field, because it will affect all Items tax after changing it. We need to change the relevant Lookup fields. Which is a lookup to a different table, right? We are changing “Tax Rate” in Invoice table, but at the same time expect the changes in “Invoices Items”.

2. We define some “shortcuts” here. See how item is repeating in the code? Hence, a “shortcut”.


When copy/paste the code, it is not obvious that “item” is not there as it should be. So the best is to look at the code we know that is working. Like Demo/Customers Client Module, etc.

Consider this example:

function on_field_changed(field) {
        if (item.field_name === 'pattern_type') {

That is not going to work. Simply because it is missing “var item = field.owner”.

3. This is where the magic happens. We “test” the field name if is the required one. If not, nothing happens and goes straight to Step 6, which is “the end”. Because this is typical “if” clause, better use “try” and “finally” in it, steps 4. and 5. respectively.

With “rec = …” we define all records needing changing, after the “taxrate” changes. With “…disable_controls” we disable all buttons (control items) temporary, as we do not want something actioned on while working on changing records.

4. Disable DOM controls temporarily. We are doing this to significantly speed up the displaying of data.

5. Then we “try” to update all records with a function “.each(…)”. Which does “edit”, “calc” and finally “post”. We use “edit” to open every single record for editing, and “post” to post everything back via API. This is the “POST” part. Without it, the data would not be saved.


The function - “calc”, does the actual calculation on records. The function is in “Journals/Invoices/InvoiceTable” Client Module.

6. Enable DOM controls.

7. And “finally”, we show the results back with “item… = rec” and enable the buttons, etc. with “…enable_controls”.

8. If the field name was not the one we are after, exit the “if” clause here.

That is it. With above steps, we created the JavaScript calculations.


No calculation will happen with a field valued as null.

It is absolutely needed to check the condition before expecting that the calculation will go through.

Error handling!

As with above null, when doing something, it is always advisable to use best practises:


One of the main obstacles with using “on_field_changed” is a infinite loop happening. For example one field changing the field we are expected to “monitor”.

The following algorithm can be used to avoid this situation:

let calculating;

function on_field_changed(field, lookup_item) {
     if (!calculating) {
        calculating = true;
        try {
             // some calculations
        finally {
            calculating = false;

For more information please visit the mailgroup error thread.

So, how was it?

Is the above too much? Or easy to follow and apply in some other scenario?

To expand a bit on “on_field_changed”, please visit Conditional formatting mailgroup thread or search the mailgroup for the same. There are a number of scenarios where to apply “on_field_changed”, and this is just a touch of the surface. One possible scenario is changing the password. Again, the best is to search the mailgroup.

More code

As mentioned, the calc function exists outside the code used in Invoices Journal. Why? Because there might be many Details in some Journals, and placing them in one single place is not flexible enough. Simply put, an function, for example on_field_changed, would overwrite the same function if there is a need for two same functions.

Invoices Details

Here is what is in the Journals/Invoices/InvoiceTable, note the on_field_changed function again:

function calc(item) {
    item.amount.value = item.round(item.quantity.value * item.unitprice.value, 2); = item.round(item.amount.value * item.owner.taxrate.value / 100, 2); = item.amount.value +;

function on_field_changed(field, lookup_item) {
    var item = field.owner;
    if (field.field_name === 'track' && lookup_item) {
        item.unitprice.value = lookup_item.unitprice.value;
    else if (field.field_name === 'quantity' || field.field_name === 'unitprice') {

function on_view_form_created(item) {
    var btn = item.add_view_button('Select', {type: 'primary', btn_id: 'select-btn'}); {
        item.alert('Select the records to add to the invoice and close the from');

function on_after_append(item) {
    item.invoice_date.value = new Date();

It needs to be stressed that details can “live” with absolutely no code on Details tree within the builder.html! However, when we attach the detail to some Journal (Tutorial. Part 3. Details), probably some code is needed there. And that is exactly the above example.

Because the details store Invoices Items, so basically products, the calc code exist here and only here. It is only relevant to Items, right?

The new functions presented here are:


It is quite obvious what the function on_view_form_created does. It creates a “Button” and assigns a function to it so when the button is clicked, it shows the JavaScript message and presents a form to select some products. All of this in just five lines of code.

Function on_after_append just adds the current date on a form.

We are almost there with the final Demo Invoices code! Hold on!

Server code

This is where Python programming language kicks in. Everything in as the Server code is Python. Just like the VBA for Access!


The last code remaining for Invoices is the Server Module code.

If we click on Journals/Invoices “Server module” as on builder.html, this is the code:

def on_apply(item, delta, params, connection):
    tracks = item.task.tracks.copy(handlers=False)
    changes = {}
    for d in delta:
        for t in d.invoice_table:
            if not changes.get(t.track.value):
                changes[t.track.value] = 0
            if t.rec_inserted():
                changes[t.track.value] += t.quantity.value
            elif t.rec_deleted():
                changes[t.track.value] -= t.quantity.value
            elif t.rec_modified():
                changes[t.track.value] += t.quantity.value - t.quantity.old_value
    ids = list(changes.keys())
    for t in tracks:
        q = changes.get(
        if q:
            t.tracks_sold.value += q

Here is where quite important function is introduced:


There are many on_apply occurrences in the official documentation since it is the same function name for the Client and Server operations, JavaScript and Python, respectively.

It is fairly small chunk of code, which mainly deals with changes, inserts or deletion of Items. The code is relevant for Demo application version at the time of writing. It can be used for many scenarios if slightly modified.

Not quite sure how to dive into the above code with the explanations with keeping it simple. The official documentation might be sufficient. However, why doing it we might ask?

Why Server Code?


To answer the above question, the Server Code is needed because it is happening in the background. It is almost exactly as the stored procedure, the relational databases are using. It is absolutely possible for an application not to use the Server Code at all, hence no need for Python. This is the application design choice. If needed, can also use the database stored procedures.

The relational database engines are quite efficient with the stored procedures. But, and there is a but, is the stored procedure portable to a different database products? Of course not. The good news is, Python is portable. Hence, with using the Server Code can successfully run on any platform.

With some other database engines, or even the Front End Applications like MS Access, for the background operations the Visual Basic might be used. With MSSQL Server, the database stored procedure can be executed from Access. Either way, the code is needed and it would not be portable to a different platform. It is “locked in” for one and only one database product. completely eliminates the above. There is no “lock in” with any specific database product. The application can be developed on any supported database, and can be moved to any supported database.

There is also a question of security. It is quite simple to implement the code which controls the permissions on what can be executed by the user. This is almost the Desktop Application territory!

And there are even more good news! Debugging.


To debug the stored procedure within the relational database, is not for faint hearted. Every developer or the DBA would agree. Of course, the more experienced professionals would protect their turf. Again, it is business after all.

However, for a “would be” developer, Python is way easier to learn and debug. And for the professionals, it is a breeze.

How to debug is completely down to Python experience, and leaving it to the reader to consult official Python resources.

And finally…

Below SQL will check for any code in the Application Server Modules (using sqlite3 command line utility):

sqlite3 admin.sqlite "select id, f_name,f_server_module  from sys_items where  f_server_module!='' and f_server_module is not null"

Below sql will check for any code in the Client Modules:

sqlite3 admin.sqlite "select id, f_name, f_web_client_module from sys_items where  f_web_client_module!='' and f_web_client_module is not null"

Of course, the Developer would need access to the admin.sqlite database to run the SQL against it. The SQL does not take into account commented code.

Here is the list where all Server code is on Demo application, hence it can be easily identified within the builder.html:

1| Demo
19|Print invoice
20|Customer purchases
22|Customer list


Note the ID! That is exactly the same ID as seen on builder.html! The Genres has ID=13, Tracks has ID=15, and so on. Hence, we know how many are there and exact location for each Server Module used within the application.

The commented out code, meaning not used at all for Demo, is code with ID=1 ( Demo). The code should be uncommented when the built in Authentication is turned on.

…the End of Code

Hope you’ve enjoyed this Code part! The intention was to explain some bits and pieces which were exclusively developed for Demo application. For sure there is more code as seen on the above SQL output! However, half of that number is related to Reports. And we are not going to Reports just yet.


Here we are introducing the basic index.html structure.


Demo application has the “About” and “” on the right hand side of the Menu bar. This is a Code as well.

However, it is a bit different to what was discussed above, since the code is related to the index.html file. Btw, this is true for everything else. Everything is based on it. It is the root file on any application in release 5 or older. In the newer release of, the file template.html is introduced. More about that latter on.

If we open index.html file in builder.html (Task/Project), the below is visible:

<div id="taskmenu" class="navbar">
    <div class="navbar-inner">
        <ul id="menu" class="nav">
        <ul id="menu-right" class="nav pull-right">
            <!--<li id="admin"><a href="#">Application builder</a></li>-->
            <li id="about"><a href="#">About</a></li>
            <li id="jam"><a href="" target="_blank"></a></li>
</div> calls this html block a Template.

The “About” and “” is simply a link, just as “Application builder” is. Which is commented out.

Hence, everything we want to see here is just a matter of adding it as extra lines. It is quite clear that this Template is related to a “menu”, and it will display on the right hand side. It clearly indicates menu-right! The id= is the most important one, here we see id admin, about and jam.

However, this itself is not enough for the proper application functioning. Why? Because the Template does not exist on it’s own. For example, in Django or Flask, the Template might be using a “built in” functionality, like “for loop” clause. does not do that. It does not contain any code for Template in the index.html file.

This must the stressed, in, there is no need to “program” the Template. It is a simple html. Ingenious.

Template Code

The Template Code for “About” and “” is in demo.js (Project/Task/Client Module). It is the default code available for any new project:

$("#menu-right #admin a").click(function(e) {
    var admin = [location.protocol, '//',, location.pathname, 'builder.html'].join('');
    e.preventDefault();, '_blank');
$("#menu-right #about a").click(function(e) {
        {title: ' framework', margin: 0, text_center: true,
            buttons: {"OK": undefined}, center_buttons: true}

The admin link will just open the new tab in the browser, with the builder.html page. Cool. So this is how we open any page, ie a “Help” page for the application. Of course, we would need a help.html file. Try it. Create help.html within the application folder and replace builder.html with help.html! Obviously, the commented code prevents this from happening. Uncomment it first.

For about link, we also see:


The function is used to find the correct Template id. In this case the below “about” block in index.html, since the id= about, which is the class name “about”:

<div class="about">
    <a href="" target="_blank"><h3></h3></a>
    <h3>Demo application</h3>
    with <a href="" target="_blank">Chinook Database</a>
    <p>by Andrew Yushev</p>

That is all to get us going. It should be easy to change the Menu content, and open some pages or a dialogue.

Wrapping up

Congrats! Almost everything is covered regarding the Demo functionality! Hope it was not that difficult.


Now that we’ve covered the Data Grids functioning, maybe it’s a time to have a look at the Demo Dashboard. First to understand is the Template in index.html for the Dashboard, and Dashboard only.

Dashboard template

The Template for Demo Dashboard is a simple two columns table:

<div class="dashboard-view">
    <div class="row-fluid">                                  <-- the table   -->
        <div class="span6">                                  <-- column #one  -->
            <canvas id="cutomers-canvas"></canvas>           <-- graphics one  -->
            <div id="customer-table"></div>                  <-- table under it -->
        <div class="span6">                                  <-- column two  -->
            <canvas id="tracks-canvas"></canvas>             <-- graphics #two  -->
            <div id="tracks-table"></div>                    <-- table under it  -->
    </div>                                                   <-- end of table-->

This obviously means one thing: we need a heaps bigger table if we are going to implement many Pie charts or similar!

Hence, the below Template would create two rows with two columns table:

<div class="dashboard-view">
    <div class="row-fluid">
        <div class="span6">
            <canvas id="assets-canvas"></canvas>
            <div id="assets-table"></div>
        <div class="span6">
            <canvas id="parts-canvas"></canvas>
            <div id="parts-table"></div>
    <div class="row-fluid">
        <div class="span6">
            <canvas id="parts_table-canvas"></canvas>
            <div id="parts_table-table"></div>
        <div class="span6">
            <canvas id="parts_suppliers-canvas"></canvas>
            <div id="parts_suppliers-table"></div>

The above example is from here second Demo.

The Demo Template above has an typo: cutomers-canvas should be customers-canvas. We can observe that this is related to a customer-table. Same with tracks-canvas, it is related to a tracks-table.

With this two pieces of information, we can build the JavaScript code to show the data. We just need to remember that index.html file must include:

<script src="static/js/Chart.min.js"></script>

The file Chart.min.js can exist elsewhere and not in static/js, however, this is where it normally resides.

Dashboard Menu Item

Before we dive into the JS code, it is visible on Demo that the Dashboard exist as an Item on the Menu, called Analytics. This is just an Project/Task/Groups Item on builder.html, where Analytics was created as an place holder for Dashboard virtual table! Now, what is a virtual table we will cover latter on, but just think of it as virtual - it does not exist in the database.

So, when creating a Dashboard from scratch, just replicate what Demo has, one Analytics (or whatever we need to call it), Group Item and one Dashboard virtual table within that Item. Simple. Takes 30 seconds to create!

Dashboard Code

As we know, uses ChartJS as the Dashboard engine. However, that does not mean much when working with the It is really easy, though.

If we look at the Demo builder.html, and click on Analytics/Dashboard/Client Code, the code has approx 100 lines. But only half of it is actually related to the application!

The rest is copy/paste! Huh, that is assuring. Cough, cough!

First, remember the above Dashboard Template, customers-canvas and tracks-canvas?

To start working on the Dashboard, we need to define this two canvases:

function on_view_form_created(item) {
    show_cusomers(item, item.view_form.find('#cutomers-canvas').get(0).getContext('2d'));
    show_tracks(item, item.view_form.find('#tracks-canvas').get(0).getContext('2d'));

The exact typo exist here as well! The code tells the Application to look at the index.html for this two canvases, and when found, the JS function executes the show_cusomers and show_tracks, respectively.

Hence, when building more graphics, we would need more functions. From above second Demo, the functions to show four graphics are:

function on_view_form_created(item) {
    show_assets(item, item.view$('#assets-canvas')[0].getContext('2d')),
    show_parts(item, item.view$('#parts-canvas')[0].getContext('2d'));

Each JS function normally pulls data from one table only. However, the ChartJS is quite effective and does Lookup fields too! So no wonder is using this library when it nicely utilises the power of lookups!

function show_cusomers(item, ctx) {                                   <-- step 1
    var inv = item.task.invoices.copy({handlers: false});             <-- step 2                                                         <-- step 3
            fields: ['customer', 'total'],
            funcs: {total: 'sum'},
            group_by: ['customer'],
            order_by: ['-total'],
            limit: 10
        function() {                                                  <-- step 4
            var labels = [],
                data = [],
                colors = [];
            inv.each(function(i) {                                    <-- step 5
                colors.push(lighten('#006bb3', (i.rec_no - 1) / 10));
            inv.first();                                              <-- step 6
            draw_chart(item, ctx, labels, data, colors, 'Ten .....'); <-- step 7
            inv.create_table(item.view_form.find('#customer-table'),  <-- step 8
                {row_count: 10, dblclick_edit: false});
    return inv;                                                       <-- step 9


  1. This is where we define the function name. It must be exact name as in the above function: on_view_form_created Yes, the same typo in the name! We try to logically name the functions as the name of the database tables.

  2. The magic happens here. We define the var inv because the table is invoices table! Very important, almost all can be used with copy/paste if we just rename a few instances of inv and invoices. Taking into account that the table fields will possibly change as well! Do not care for handlers: false just yet.

  3. Remember, the var inv is just a “shortcut” (I call it like that, because I like it). Hence, will open the table invoices for access. By opening, we are actually fetching a few fields (Chinook database fields) :

    • customer and total

    We also using a “built in” ChartJS Function, funcs:

    • to summarise total with function ‘sum’

    Can we read the rest what is happening? Order and limit? I hope so. It is quite simple, right?

  4. This code is copy/paste, it is needed for ChartJS.

  5. Remember to adjust inv, customer and total for your needs. This is also where Lookup fields kicks in, the customer.display_text will display the Customer Name and not the CustomerId as an Integer, which is visible on the Invoices table. Also worth mentioning is total.value.toFixed(2), which is obviously a value of Total, rounded to two decimals. Since we dealing with 1/10 of data, some calculations is needed with (i.rec_no - 1) / 10).

  6. Start from first. Remember inv!

  7. Draw graphics with ‘Ten most active customers’ Label!

  8. Create table with some data under the graphics within the customer-table Template! Remember to adjust for the rest of Templates!

  9. And finally pass back the Invoices data.

So there you go! With one single block of code we can create indefinite number of graphics utilising Lookup fields within the ChartJS!

Cosmetic code

Now that we know how to create a graphics for one table, the rest is a copy/paste! Functions draw_chart and lighten is just cosmetic. Play with it.


Changing a code in function draw_chart from a ‘pie’ to ‘bar’, will change the graphics!

End of Dashs

Once we get a handle of it, creating a Dashboards is a breeze! It is just a matter of adjusting the names of tables and variables. Plus, how much data we are presenting, one tenth of it, or one third, etcetera. Of course, there is a bit more about it, but we do not want the information overload! Hope that this is just enough to get going!


To Be Continued…