Understanding the Lianja HTML5 Client

PDF Print E-mail

Lianja Web and Mobile database Apps are RIA (Rich Internet Applications) that are automatically generated for you by the Lianja App Builder which does most of the heavy lifting of UI design, UI layout and UI navigation for you.

There are many Web and Mobile Apps that can be built without any coding at all with the Lianja App Builder using drag 'n' drop -- we call this NoCode™ App Building.

If you are a hardcore Web or Mobile App developer and you need to develop a more complex App where the standard built-in sections and functionality of Lianja require some custom code to be written, then the Lianja HTML5 Client API is what you need to familiarize yourself with.

Lianja Web and Mobile Apps work in conjunction with the Lianja Cloud Server. Please read An Introduction to Lianja Cloud Server before reading this article if you have not yet done so.

Contents

Introduction
The Lianja HTML5 Client API
Lianja/VFP Server Pages
Lianja JavaScript Server Pages
The Lianja System Object
Understanding Synchronous and Asynchronous requests
Lianja Web Client built-in Lianja/VFP functions
Building Canvas Sections for Web and Mobile
Building Custom Sections for Web and Mobile
What happens when several users try to update a record at the same time
Custom CRUD OData Data Access
Working with local data cursors in the Lianja Web Client
Understanding macro substitution in the Lianja Web Client
How to determine whether the App is running in development "View" or "Live"
My App does not display how do I debug it
Viewing your App live in a desktop browser
Viewing your App live on a Tablet or a Phone

Introduction

Lianja Apps are all organized logically into an object model very similar to the DOM (Document Object Model) that standard web browsers parse and render. This object model is known as the Lianja Object Model (LOM) and this is at the heart of Lianja.

Lianja Apps are made up of pages; pages are made up of sections; and sections are made up of fields and gadgets (known as formitems). There is a wide range of built-in sections in Lianja that function across Desktop, Web and Mobile Apps.

Lianja "best practices" dictate that you should use standard built-in sections whenever possible.

Your users navigate between pages using the pages menu, the navigation panel or through your own custom navigational logic that you can control in delegates.

If you have not yet done so please read through Understanding the Lianja App Architecture and Working with the Lianja Object Model.

Lianja Web and Mobile Apps are generated as HTML5 JavaScript clients which have included in them the Lianja HTML5 Client. The Lianja HTML5 Client has a feature rich set of JavaScript objects and functions that you can call from any event delegates that you have specified in your App while developing it in the Lianja App Builder.

Lianja Web and Mobile database Apps have the Lianja HTML5 Client built into them. This is comprised of:

  • Lianja Web Framework
  • jQuery
  • jQuery Mobile
  • Twitter Bootstrap
  • PhoneGap
  • underscore

Let's take a look at a typical Tablet App in a "Tablet App View" in the Lianja App Builder that was primarily built with NoCode™.

Screen Shot 2014-03-29 at 2.43.45 PM

And now the same App running live in Google Chrome.

Screen Shot 2014-03-29 at 3.07.58 PM

The Lianja HTML5 Client API 

The Lianja HTML5 Client API is a feature rich set of JavaScript objects and functions that let you interact with the Lianja Object Model at a high level of abstraction.

Now before you say "Oh no not JavaScript" read on...

For Lianja/VFP developers, the Lianja HTML5 Client API includes many of the Lianja/VFP functions and classes (The Lianja UI Framework which is based on the core VFP 9 UI classes) that you are already familiar with in Lianja/VFP as well as many functions that PHP developers will already be familiar with.

Lianja Apps are all about ART (Actions, Rules and Transitions). Read this article for an explanation of this.

Whether building Desktop, Web or Mobile Apps in Lianja you can execute your own functions (known as delegates) when actions occur e.g. a user changes some data in a field or clicks on a menuitem or button. In desktop Apps these delegates can be written in Lianja/VFP, PHP, Python or JavaScript.

In Web and Mobile Apps these delegates need to be written in JavaScript -- the language of the browser.

In your JavaScript delegates you can call the built-in functions and object methods that are available in the Lianja HTML5 Client API.

Additionally you can make both synchronous and asynchronous calls to server-side business logic (procedures or functions) that are written in Lianja/VFP or JavaScript (soon PHP and Python too) and return the result to your JavaScript code.

So the essence of what you are doing is building a multi-tier client/server App with its UI presentation layer running in the browser and its business logic and data access running on the server -- under the control of the Lianja Cloud Server.

Now you may be wondering, how can I build one App that runs in Desktop, Web and Mobile. This is all accomplished using UI personalities. Each UI element in your App can be included or excluded from the target client when the Lianja App Builder generates the code for it.

When developing a Web or Mobile App, firstly tell the App Builder to use JavaScript for the App (This can be set at the App, Page and Section level).

app Screen Shot 2014-03-29 at 3.19.39 PM

Ok got that, so how do I see the delegates?

delegates Screen Shot 2014-03-29 at 3.16.25 PM

Tip: You just need to click the "..." button to the right hand side of the delegate and a unique name will be chosen for the delegate function and it will be added to the JavaScript custom library then the editor will be displayed allowing you to edit your JavaScript delegate code. Switching back to the "Pages" design view will automatically save the the code for you. 

How do I tell the App Builder if a certain Page, Section or Formitem should be included in Desktop, Web, Tablet or Phone Apps?

ui Screen Shot 2014-03-29 at 3.15.49 PM

Is there an easier way to navigate around my App and edit the delegates? Yes, use the App Inspector. That's what it's for.

ins Screen Shot 2014-03-29 at 3.28.37 PM 

Now let's take a quick look at what is available in the Lianja HTML5 Client API and how you can make best use of this in your JavaScript delegates when building Web and Mobile Apps.

Lianja/VFP Server Pages

Lianja Cloud Server has built-in support for Lianja/VFP (Extended Visual FoxPro) Dynamic Server Pages.

Dynamic server pages contain HTML and page processing directives in the same way as a PHP page does. You use page processing directives to generate HTML code dynamically.

Unlike traditional PHP server pages, Lianja/VFP pages (.rsp pages) have the Lianja database and scripting engine embedded in them. This makes for very powerful and flexible RAD Web and Mobile database App development.

You can generate dynamic content into a standard "WebView" section with server-side code written in Lianja/VFP.

Full details about the Lianja/VFP server pages can be found in the article An introduction to Lianja Cloud Server.

Lianja JavaScript Server Pages

Lianja Cloud Server also has built-in support for JavaScript Server Pages.

JavaScript Server Pages (.jssp pages) are modelled on PHP Server Pages. They embed the V8 JavaScript engine which is used by Google Chrome and Node.js. The JavaScript engine implements ECMAScript as specified in ECMA-262, 5th edition.

Lianja JavaScript Server Pages provide the ability to write both the client and the server code in JavaScript. You can use them in place of PHP server pages. If you know JavaScript and HTML these are for you. You are not required to learn any new programming language in order to build custom Web and Mobile database Apps.

JavaScript Server Pages have embedded Lianja database and OData support and they are fast as the whole engine is written in C/C++, no Java or other dependencies required.

Full details about Lianja JavaScript server pages can be found in the article An introduction to Lianja Cloud Server.

The Lianja System Object

If you have not yet done so read the article Understanding the Lianja App Architecture first.

Lianja Apps are built out of pages. Pages are built out of sections. There are a wide variety of built-in sections. Form sections for example are made up of Fields and Gadgets. So as we can see, a Lianja App consists of a hierarchy of visual elements. This hierarchy we will refer to as the Lianja Object Model (LOM).

You can consider the whole of the Lianja App Builder as a meta-framework that you can use to develop Desktop, Web and Mobile database Apps with.

As a developer you can manipulate the properties and call methods on any of these visual elements in both development and runtime mode. This is accomplished using the Lianja system object in any if the supported scripting languages; Lianja/VFP, Python, PHP or JavaScript.

The following methods and properties are part of the Lianja system object for JavaScript in the Web and Mobile clients. There are many other undocumented ones but I would recommend that you only use what is documented as the others are subject to change;

FunctionArgumentsDescription
Lianja.createObject() class as string  Create an new object based on the specified UI class
Lianja.evaluate() expression as string  Evaluate the specified expression on the server in the context of the current App
Lianja.evaluateJavaScript() functioncall as string  Call server-side JavaScript function and return any result. (From v2.0.0). e.g.
Server: The file myfunction.js should be in the the running App's directory or in the ...\wwwroot\library directory and contain a function with the same name as the file.
function myfunction(arg)
{
  // do something useful...
  return retval;
};
Client: Call a server-side JavaScript function called myfunction from a client delegate
var ret = Lianja.evaluateJavaScript("myfunction('hello')");
Note that if the file myfunction.js file does not exist in the App's or library directory, but the file server_functions.js does, then the function myfunction is assumed to be defined in server_functions.js.
Lianja.get() elementID as string  Synonym for Lianja.getElementByID() 
Lianja.openDatabase() database as string Returns an object reference to a dynamic database cursor when used in the
following way e.g.
var db = Lianja.openDatabase("southwind");
var cursor = db.openRecordSet("customers");
You can now call any of the documented methods on the cursor
and reference the cursor properties.

To perform queries set the filter property then call:
cursor.filter = "name = 'smith' and value > 1000";
cursor.moveFirst();

This will make a synchronous call to the server. Whenever possible
we recommend you use Async calls by specifying onsuccess and
onerror callbacks. (See cursor doc further on in this article).

Lianja.getCurrentPosition() onsuccess as function
onerror as function
This acts a a wrapper for the HTML5 GeoLocation API. onsuccess is called with
an object containing lattitude,longitude,alltitude etc. In Desktop Apps this
is ignored but will not throw any errors so you can develop and test on the
Desktop then use in Web or Mobile Apps.
Lianja.getCursor() tablename as string Returns an object reference to an internal cursor e.g.
  var cursor = Lianja.getCursor("customers");

You can now reference data in the cursor like this:

  var name = cursor.getData('name');
  cursor.setData('name', expression);
Lianja.getElementByID() elementID as string  See below 
Lianja.Odata_Create() url as string
data as jsonstring|object
callback as function
args as object (optional) 
Inserts a new record into a database table on the server with the
specified data values. This is an Async call so you should provide
a callback function that will be called on completion.
Lianja.Odata_Delete() url as string
data as jsonstring|object
callback as function
args as object (optional) 
Deletes a record from a database table on the server with the
specified data values. This is an Async call so you should provide
a callback function that will be called on completion. 
Lianja.Odata_Read() url as string
callback as function
args as object (optional) 
Fetches rows from a database table on the server. This is an Async
call so you should provide a callback function that will be called on completion. 
Lianja.Odata_Update() url as string
data as jsonstring|object
callback as function
args as object (optional) 
Updates one or more records in a database table on the server with the
specified data values. This is an Async call so you should provide
a callback function that will be called on completion. 
Lianja.showDocument() action as string  See below 
Lianja.showErrorMessage() message as string
[, title as string]
Display specified message (Failed theme)
Lianja.showNotification() message as string
[, title as string]
Display specified message (Message theme)
Lianja.showMessage() message as string
[, title as string]
Display specified message (Information theme)
Lianja.hideMessage() none Hides any messages that are currently displayed
Lianja.showSuccessMessage() message as string
[, title as string]
Display specified message (Success theme)
Lianja.writeError() message as string  Writes the message to the JavaScript console 
Lianja.writeLog() message as string Writes the message to the JavaScript console 
Lianja.writeOutput() message as string  Writes the message to the JavaScript console 

The following examples show you how to work with the Lianja Object Model (LOM) in JavaScript in Web and Mobile Apps. 

Notice that when working with objects, methods and properties in Lianja it is very similar for all of the supported scripting languages. The only real difference is that you must remember to put a ; at the end of JavaScript statements.

Lianja.getElementByID() / Lianja.get()

You can obtain a reference to the visual elements that make up your App using Lianja.getElementByID( item ) .

To get a reference to a page with the id mypage.

var item = Lianja.getElementByID("mypage");

To get a reference to a formitem field2 in the section mysection contained within the page mypage.

var item = Lianja.getElementByID("mypage.mysection.field2");

Once you have a reference to a UI element you can manipulate its properties and call its methods.

var item = Lianja.getElementByID("mypage.mysection.field2");
var text = item.text;
item.text = upper(text);

Lianja/VFP developers notice how I am using var which is similar to local in Lianja/VFP

Lianja.showDocument()

showDocument() provides the ability to perform a wide variety of actions on UI elements with minimum coding. You will find that this method on the Lianja system object is commonly used in your Apps.

It has a wide range of uses and functionality. Let's take a look at some of these.  

ArgumentDescriptionExample
page:id Show page with the specified id Lianja.showDocument("page:page1");
page:id?action=action Perform the specified action on the page (primary section)
with the specified id

Action
can be:

add
delete
edit
editmode
first
last
next
previous
save
cancel
refresh
search&text=value
filter&text=expression
Lianja.showDocument("page:page1?action=refresh");
page:pageid.id?action
(or for current Page)
section:id?action
Perform the specified action on the section
with the specified id

Action can be:

add
delete
edit
editmode
first
last
next
refresh
hide
show
print
search&text=value
filter&text=expression
select
select&text=tabcaption
showsearchpanel
hidesearchpanel
togglesearchpanel
Lianja.showDocument("page:page1.section2?action=next");

or

Lianja.showDocument("section:section2?action=previous"); 

Understanding Synchronous and Asynchronous requests

When building Web and Mobile Apps that make calls to the server to execute business logic procedures, fetch and update data and populate our WebViews and other UI elements, we need to make sure we don't "lock up" the browser while these calls are in progress otherwise our UI would become unresponsive on slow lines and the user would see the UI being updated at sporadic intervals. They would not even be able to maximize a window in a desktop browser!

To overcome this, all calls to the server are made asynchronously, which basically means that the request is dispatched and the program continues executing. So that being the case, how do we know when an operation has completed and how does Lianja handle such things as section relationships?

Let me explain.

The overall concept behind the Lianja UI on desktop is based on pages being made up of sections and each section can have one or more child sections. So in order to correctly relate information between sections Lianja queues up async calls and as they each complete and the state for a section is known, it will continue dispatching requests to the server to fetch and populate related content. So in other words, it keeps an internal queue of pending requests which are dependent on the result of previous requests.

If we need to execute a business logic procedure in an App on the server and perform some operations once that request is completed we accomplish this using callbacks.

Let's look at an example where we want to retrieve a customer balance from the server which requires the server-side procedure to perform a variety of SQL operations, calculate the balance and send it back to the client. This is where Lianja.evaluate() comes in to play.

Lianja.evaluate(("calculateCustomerBalance('{customers.name}')"),
    function(result)
    { Lianja.getElementByID("mypage.mysection.myfield").text = result;
    },
    function(errormessage)
    {
    }
);
// execution continues before the result is returned

There are occasions however when you need to evaluate something on the server and wait until the request has completed before continuing execution of your JavaScript. So now let's look at the same synchronous call that will block until the result is returned.

result = Lianja.evaluate("calculateCustomerBalance('{customers.name}'");

Notice how we can use {table.column} macros to substitute the values from the current data cursors that are maintained by the client

Lianja Web Client built-in Lianja/VFP functions

The following Lianja/VFP functions are included in the Lianja HTML5 Client API for JavaScript;

FunctionArgumentsDescription
addslashes() arg as string Quote string with slashes
alltrim() arg as string Remove leading and trailing spaces
at() substring as string,
source as string
[, start as numeric]
Search for substring and return position
base64_decode() text as string Function to decode a text string from MIME base64
base64_encode() text as string Function to encode a text string to MIME base64
between() value as expression
low as expression
high as expression
Function to check that a given value is within a given range
cdow() source as date Function to return the day of the week from the specified date as a string
chrtran() source as string
find as string
replacewith as string
Function to replace text within a string
cmonth() source as date Function to return the month from the specified date as a string
createObject() class as string Create new object based on the specified UI class
ctod() source as string Function to return the specified string as a date
date() [year as numeric, month as numeric, day as numeric] Function to return system or the specified date as a date
day() source as date Function to return the day of the month from the specified date as a numeric
dow() source as date Function to return the day of the week from the specified date as a numeric
dtoc() source as date Function to return the specified date as a string
dtos() source as date Function to return the specified date as a string in the format YYYYMMDD
empty() arg as expression Returns true if the given expression is empty i.e. an empty string, a numeric zero of a boolean false.
endsWith() source as string,
substring as string
Check if source string ends with substring
etos() source as expr
[, length as numeric]
Evaluate expression and return it as a string, optionally specifying length
explode() separator as expression,
string as expression
Returns an array by splitting the given string up as specified by the given separator
function_exists() arg as string Returns true if the specified function is declared
guid() none Returns a GUID
icase() condition as logical,
result as expr
[, ...]]
otherwiseresult
Execute immediate case statement. Evaluates a condition then
if it is true returns the next argument in the argument list. If
no conditions are true returns the otherwiseresult.
iif() condition as logical,
expression1 as exp,
expression2 as exp
Execute an immediate if. Returns expression1 if condition is true
otherwise returns expression2
implode() separator as expression,
array as expression
Returns a string of all items in the given array separated by the given separator.
in_array() value as expression,
array as expression
Returns true if the given value is in the specified array.
indexOf() source as string,
substring as string
Search for substring and return position
inlist() source as string,
list as string
Check if source string is contained in a list of arguments.
is_array() arg as expression Returns true if the given arg is an array.
is_date() arg as expression Returns true if the given arg is a date.
is_float() arg as expression Returns true if the given arg is a number.
is_function() arg as expression Returns true if the given arg is a function.
is_int() arg as expression Returns true if the given arg is a number.
is_logical() arg a expression Returns true if the given arg is a boolean.
is_numeric() arg as expression Returns true if the given arg is a number.
is_object() arg as expression Returns true if the given arg is an object.
is_string() arg as expression Returns true if the given arg is a string.
len() source as expr
[, options as logical]
Return length of expression
loadlibrary() fileURL.js as string Load the specified JavaScript file
lower() arg as string Convert to lower case
lpad() source as string,
length as numeric
[, padchar as string]
Pad string to defined length from left
ltrim() source as string Remove leading spaces
md5() source as string MD5 encypts the specified string
messagebox() message as string
[, options as numeric
[, title as string]]
Display a dialog box and continue execution
month() source as date Function to return the month from the specified date as a numeric
padl() source as string,
length as numeric
[, padchar as string]
Pad string to defined length from left
padr() source as string,
length as numeric
[, padchar as string]
Pad string to defined length from right
proper() source as string Convert to proper case
reccount() alias as string Return record count
recno() alias as string Return current record number
replicate() arg as expression,
count as expression
Returns a string which is a replication of the given arg 'count' times.
require() jsfileURL as string Load the specified JavaScript file
require_once() jsfileURL as string Load the specified JavaScript file (if not already loaded)
rowcount() alias as string Return row count (excludes deleted and those not matching current filter)
rpad() source as string,
length as numeric
[, padchar as string]
Pad string to defined length from right
rtrim() source as string  Remove trailing spaces
startsWith() source as string,
substring as string
Check if source string starts with substring
str() source as numeric
[, width as numeric
[, decimals as numeric]]
Convert numeric to string
strlen() source as expr
[, options as logical]
Return length of expression
strtran() source as string
find as string
replacewith as string
Function to replace text within a string
str_replace() substring as string,
replacestring as string,
source as string
Search for substring and replace with replacestring in source
substr() source as string,
start as numeric
[, end as numeric]
Extract substring
time() none Function to return current system time
tostring() arg as expression Returns the given arg as a string type.
trim() arg as string Remove leading and trailing spaces
upper() arg as string Convert to upper case
val() arg as string Convert string to numeric
year() arg as date Function to return the year from the specified date as a numeric

Others will be added soon.

Building Canvas Sections for Web and Mobile

You can build JavaScript "Canvas" sections in the Lianja App Builder. The most important thing to remember when you are building canvas sections for the Web and Mobile client is that you need to specify a unique name for each UI field on your canvas. These names are accessible globally in JavaScript so a good practice is to prefix them with something meaningful e.g. m_contactname. In Web and Mobile Canvas sections you can reference the UI controls and their properties in your delegates just as you would in the desktop client e.g. 

messageBox("The value of the contact name is " + m_contactname.text);
m_contactname.text = "This is a new value";

You can hide and show UI controls using the standard Lianja methods.

m_contactname.hide();
m_contactname.show();

And change their standard properties just as you would in the Desktop client.

m_contactname.backcolor = "lightgreen";
m_contactname.forecolor = "#afafaf";

Additionally, you can use the standard Lianja/VFP UI framework syntax. Let's say we have a "Container" called m_mycontainer in a canvas section.

m_mycontainer.addObject("m_mytextbox", "textbox");
m_mytextbox.top = 50;
m_mytextbox.left = 20;
m_mytextbox.width = 100;
m_mytextbox.height = 24;

And so on...

Building Custom Sections for Web and Mobile

If you need to build a more complex Web or Mobile App with a custom UI you do this by building Custom Sections in JavaScript in the Lianja App Builder. See the article Developing Custom Sections in JavaScript for details on how to do this.

What happens when several users try to update a record at the same time?

The Lianja Web Client maintains a "before" values buffer of the data that was read from the Lianja Cloud Server when data is displayed in the UI. When a record is updated by a user, the OData call that is made to the Cloud Server includes the "before" values of the fields that have been changed. The Lianja Cloud Server then uses optimistic locking to perform the update and will reject the update if the fields being updated have changed since they were last read by the user. This provides for optimum concurrent performance as it is essentially handling field level locking. This allows several users to update a record at the same time as long as they don't update the same fields which is unlikely anyway.

Custom CRUD OData Data Access

If you build your Web and Mobile Apps in the Lianja App Builder the Lianja Web Client will automatically bind data for you so that when you Add new records, Update records, Delete records or navigate/search for records in a page the data is automatically refreshed in the UI and automatically updated by making OData calls to the Lianja Cloud Server.

If you need to perform custom CRUD operations (Create, Read, Update, Delete) you do this using the OData functions available in the Lianja HTML5 Client API or perform operations on local cursors (read about these further down in this article).

This article Working with OData in Lianja Cloud Server provides details on how to contruct OData URIs. 

OData create a new record

As with all the Lianja OData calls you specify an OData URL, followed by the data as a JavaScript object or JSON string, the callback function which will be called on completion and an optional args object which you can use to maintain any state or add additional information that will be passed to your callback function.

Lianja.OData_Create(
url,
data,
function(status, result, args)
{
},
args);

Example:

Lianja.OData_Create(
"/odata/yourdatabase/yourtable?$rowid", // inserts a new row and returns all columns
{ "name": "value", "amount":25.67, "date": "20140404" },
function(status, result, args)
{
if (status) { // success
}
else { // failed
}
},
args);

OData read records  

As with all the Lianja OData calls you specify an OData URL, followed by the data as a JavaScript Object or JSON string, the callback function which will be called on completion and an optional args object which you can use to maintain any state or add additional information that will be passed to your callback function.

Lianja.OData_Read(
url,
function(status, result, args)
{
},
args);

Example:

Lianja.OData_Read(
"/odata/yourdatabase/yourtable?$top=1&$skip=20&$rowid", // fetches row 21.
function(status, result, args)
{
if (status) { // success
}
else { // failed
}
},
args);

OData update a record

As with all the Lianja OData calls you specify an OData URL, followed by the data as a JavaScript object or JSON string, the callback function which will be called on completion and an optional args object which you can use to maintain any state or add additional information that will be passed to your callback function.

Lianja.OData_Update(
url,
data,
function(status, result, args)
{
},
args);

Examples:

Lianja.OData_Update(
"/odata/yourdatabase/yourtable?$rowid=10", // updates record 10
{ "name": "value", "amount":25.67, "date": "20140404" },
function(status, result, args)
{
if (status) { // success
}
else { // failed
}
},
args);

If you need the Cloud Server to handle optimistic locking when updating records then you need to specify the "__olddata" sub-object which contains the column names and values from the last time you read the data.

Lianja.OData_Update(
"/odata/yourdatabase/yourtable?$rowid=10", // updates record 10
{ "name": "value", "amount":25.67, "date": "20140404", "__olddata" : { "amount":20.00, "date": "20140404" } },
function(status, result, args)
{
if (status) { // success
}
else { // failed
}
},
args);

OData delete a record

As with all the Lianja OData calls you specify an OData URL, followed by the data as a JavaScript object or JSON string, the callback function which will be called on completion and an optional args object which you can use to maintain any state or add additional information that will be passed to your callback function.

Lianja.OData_Delete(
url,
data,
function(status, result, args)
{
},
args);

Example:

Lianja.OData_Delete(
"/odata/yourdatabase/yourtable?$rowid=10", // deletes record 10
{ "name": "value", "amount":25.67, "date": "20140404" },
function(status, result, args)
{
if (status) { // success
}
else { // failed
}
},
args);

Working with local data cursors in the Lianja Web Client

The Lianja HTML5 Client maintains local data cursors for tables that are actively being used in sections within pages. Each local data cursor has the concept of an "Active Record". As you navigate data and the sections are refreshed  the "Active Record" for these local data cursors is kept in sync with what is displayed in the UI. In desktop apps, the Lianja/VFP database engine provides direct access to these using the familiar alias.fieldname notation.

In your Web and Mobile database Apps you can also get access to this data using Lianja.getCursor("tablename") and then use the getData() and setData() methods on the cursor object that is returned from Lianja.getCursor().

Additionally, seeing as the cursor state is known at any time you can use {alias.fieldname} macros in your validation expressions and any URLs that you have associated with WebView sections. Note that if you create a dynamic local cursor using Lianja.createCursor() then macros will not be evaluated against this cursor, you need to do that manually in your custom JavaScript code.

Local cursors provide the ability to dynamically query and refresh section contents based on the values of fields in other sections.

var orders = Lianja.getCursor("orders");
var name = orders.getData("name");
var unitcost = orders.getData("unitcost");
var quantity = orders.getData("quantity");
orders.setData("total", unitcode * quantity);
orders.update(
function() {
// onsuccess
Lianja.showSuccessMessage("Record was updated");
Lianja.showDocument("page:orders?action=refresh");
},
function() {
// onerror
Lianja.showErrorMessage("Record could not be updated", "Update failed");
}
);

The example above shows you how to update data on the server using local cursors. The "Cursor" object has methods for create(), read(), update() and _delete(). Notice how the delete method is prefixed with an "_" as delete is a reserved word in JavaScript. Each of these methods takes two (optional) arguments (known as closures) that are functions that are called when the operation completes (onsuccess, onerror).

The following methods and properties are available in a Cursor object. Notice that some of the methods have synonyms.

NameArgumentsDescription
add() none Clears the internal record buffer in preparation for insert() being called
after setData(field, expr)...
insert() onsuccess as function,
onerror as function
[, args ] 
Creates a new record in the database table on the server containing
the column data values that were changed by setData(). You should use
in conjunction with add() and setData()...

If this is called with no arguments (i.e. no callbacks) it will be
executed synchronously. 
read()

onsuccess as function,
onerror as function
[, args ] 
Reads a record from the database table on the server based
on the current recno. If the recno is > than the reccount
then the last record will be read.

If this is called with no arguments (i.e. no callbacks) it will be
executed synchronously. 
update() onsuccess as function,
onerror as function
[, args ] 
Updates the current record in the database table on the server containing
the column data values that were changed by setData().

If this is called with no arguments (i.e. no callbacks) it will be
executed synchronously. 
_delete(); onsuccess as function,
onerror as function
[, args ] 
Reads a record from the database table on the server based
on the current recno.

If this is called with no arguments (i.e. no callbacks) it will be
executed synchronously. 
refresh() onsuccess as function,
onerror as function 
Refreshes the active record of the cursor from the server
based on the current filter and searchfilter conditions or
the current recno if neither are specified.

If this is called with no arguments (i.e. no callbacks) it will be
executed synchronously. 
moveFirst() onsuccess as function,
onerror as function 
Positions on the first record in the cursor then refreshes
the active record of the cursor from the server.

If this is called with no arguments (i.e. no callbacks) it will be
executed synchronously. 
moveNext() onsuccess as function,
onerror as function 
Positions on the next record in the cursor then refreshes
the active record of the cursor from the server.

If this is called with no arguments (i.e. no callbacks) it will be
executed synchronously. 
movePrevious() onsuccess as function,
onerror as function 
Positions on the previous record in the cursor then refreshes
the active record of the cursor from the server 
moveLast() onsuccess as function,
onerror as function 
Positions on the last record in the cursor then refreshes
the active record of the cursor from the server.

If this is called with no arguments (i.e. no callbacks) it will be
executed synchronously. 
moveBookmark() record as number
onsuccess as function,
onerror as function
Positions on the specified record in the cursor then refreshes
the active record of the cursor from the server.

If this is called with no arguments (i.e. no callbacks) it will be
executed synchronously.

getData() column as string Get the value of the given column from the active record
setData() column as string,
value as expression
Set the value of the given column in the active record
recno()   get the current rowid in the cursor 
reccount()   get the total row count in the cursor 
bof   returns true if the cursor is at BOF (Beginning Of File) 
eof   returns true is the cursor is at EOF (End Of File) 
found   returns true if a search for a record was successful 
filter   get/set the filter condition on the cursor.
You need to call refresh() to refresh the active record. 
searchfilter   get/set the searchfilter condition on the cursor.
You need to call refresh() to refresh the active record. 
values   returns an object which has a name and value for each
column in the underlying data for the active record 
changedvalues   returns an object which has a name and value for each
column in the underlying data for the active record that
has been changed since the record was last read or updated
in the UI or programatically. 
database   get the database name that this cursor is associated with 
table   get the table name that this cursor is associated with 

Understanding macro substitution in the Lianja Web Client

As we saw in an earlier example in this article, just as you can use {expression} macros in the Lianja Desktop Client you can also use these in your Web and Mobile Apps.

Lianja.evaluate("calculateCustomerBalance('{customers.name}'"),
    function(result)
    { Lianja.getElementByID("mypage.mysection.myfield").text = result;
    },
    function(errormessage)
    {
    }
);
// execution continues before the result is returned

 Macros are evaluated from left to right:

Lianja.evaluate("calculateCustomerBalance('{customers.name}','{customerid.id'}"),
    function(result)
    { Lianja.getElementByID("mypage.mysection.myfield").text = result;
    },
    function(errormessage)
    {
    }
);
// execution continues before the result is returned

Macros can be nested so that the inner macros are evaluated before the outer macros. This provides the ability to query information from the server and have that information substituted into another call to the server.

Lianja.evaluate("calculateCustomerBalance('{customers.name}', \"{getCustomerID('{{customers.custid}}')}\")),
    function(result)
    { Lianja.getElementByID("mypage.mysection.myfield").text = result;
    },
    function(errormessage)
    {
    }
);
// execution continues before the result is returned

 This will result in the following macro substitutions being performed in this order.

{{customers.custid}} lets call this result3
{customers.name} let's call this result1
{getCustomerID("result3")} let's call this result2
Lianja.evaluate("calculateCustomerBalance('result1', "result2")

Bear in mind that just as in the Lianja Desktop Client we can specify validation expressions that use {...} macros and also use {} to be substituted for the current value of the UI control being validated.

How to determine whether the App is running in development "View" or "Live"

If you need to determine whether or not the Web/Mobile App is running in a development mode "View" or whether it is live in the Cloud you can just test like this:

if (typeof LianjaAppBuilder === 'object')
{
// handle running in development mode
}
else
{
// handle runtime mode in the cloud
}

My App does not display how do I debug it?

You click the "Debug" icon in the "Web App View" toolbar on the left and inspect the error messages in the "Console". You can then set breakpoints and watch variables.

Screen Shot 2014-04-05 at 11.20.37 AM 

Viewing your App live in a desktop browser.

To view your App live in a desktop browser (against your live data) click the "Preview" icon.

 Screen Shot 2014-04-05 at 11.21.22 AM

Viewing your App live on a Tablet or a Phone. 

After viewing your App live in a desktop browser (using "Preview") most of the files that your App is using will automatically be deployed. If you have any custom libraries and database you need to deploy them using the "Deploy" workspace.

If you are using a tablet or phone, turn wifi on and then point your tablet or phone browser to the URL of the App on your development machine. You should be prompted to "Add this web page to the homescreen". If you accept this then a homescreen icon will added (the icon and icon title are specified in the App settings). Touching on this will then run your App full screen on your tablet or Mobile phone.

Support forums

If you have any issues or just want to know "How to do something" then ask on the Developer Community Forums.

A word about Sponsored Features

Does Lianja meet every requirement you have but one, or is there specific functionality that we have on the roadmap that you need in order for you to reduce your development times?

Don't let a small feature gap force you to use another solution that is inferior overall. We can deliver feature enhancements to Lianja that meet your specific requirements and your specific deadlines.

For any general-purpose functionality that you would otherwise have to build and maintain yourself, or wait until that feature is made available as stated in our development roadmap, feature sponsorship is a highly cost effective way to shorten your project schedule and reduce the amount of code you maintain.

Contact us today about sponsoring the development of Lianja features.