Standalone Executables on Windows

From Lianjapedia
Jump to: navigation, search

See Also

Deployment, Lianja Package Manager, Packaging Lianja Desktop Apps for Windows

Overview

The range of Apps that can be built as standalone executables on Windows and the ease and speed of building and deploying the executables is significantly extended from Lianja 9.5.

  • Any Lianja App can be packaged as a standalone desktop App for Windows (previously just Apps based on a custom-coded or visually built autogenerated Form as below). Three of the sample Apps included in the App Builder distribution are provided as examples:
  • Build and run a standalone App with one click or customize the build with the following options:
    • Autogenerated and/or defined library dependencies
    • Create a zip or self-extracting exe
    • Specify database(s) to include
    • Specify mandatory login with optionally customized login page
  • Build in release mode or debug mode, the latter displaying a command window and output panel alongside the App
  • Include a pre-load splashscreen image

Pre 9.5

From Lianja 7.0 developers can build standalone executables on Windows.

These executables can only be built for Apps that have been hand-coded in LianjaScript, Python, JavaScript, TypeScript or Babel using the Lianja UI Framework.

Alternatively, you can also visually build forms in the App Builder using any of the following scripting languages as the Lianja UI framework is scripting language independent across Desktop, Web and Mobile Apps.

  • LianjaScript/VFP
  • Python
  • JavaScript
  • Typescript

Forms are based on a tabview section which contains canvas, grid or webview based sections in its tabs. Working with Forms in Lianja for further details.

This functionality is primarily for building small GUI Apps, not for business Apps running under the Lianja App Center with Users and Roles.

Sample Apps

Sample App: example_timeclock

The example_timeclock sample App is included in the Lianja App Builder distribution from v7.0 in the 'General' category.

This app uses the Camera UI Framework class and is a small App to handle clocking in and out of employees in a company.

Sample App: example_timeclock


It has been further enhanced from v9.5 and demonstrates a custom coded (Visual Foxpro/Python/JavaScript) Form based standalone App.

Sample App: form2

The form2 sample App ('Tabbed Form'), included in the Lianja App Builder distribution, has been updated in v9.5 to demonstrate how an autogenerated Form based App can be built as a standalone desktop App for Windows.

Sample App: form2


Note the following updates:

  • App Settings -> Standalone Options
  • App Settings -> UI Presentation Rules
  • app.conf file (Category: Other files)
  • splashscreen.png (Category: Image files)

Sample App: example_webapp1

The example_webapp1 sample App ('Tablet Demo1'), included in the Lianja App Builder distribution, has been updated in v9.5 to demonstrate how a non Form based App - and one that also runs on web/mobile - can be built as a standalone desktop App for Windows.

Sample App: example_webapp1


Note the following updates:

  • App Settings -> Standalone Options
  • App Settings -> UI Presentation Rules
  • app.conf file (Category: Other files)
  • splashscreen.png (Category: Image files)

Building Standalone Executables

Scripting Language

The example_timeclock App includes sample custom coded Forms in Visual Foxpro, Python and JavaScript.

Select the scripting language in the App Settings to build for the selected language:

Scripting Language


Standalone

Check Standalone in the UI Presentation Rules App Settings.

UI Presentation Rules


Standalone Options

Check the Standalone App setting to generate a complete standalone App for any Windows desktop App (like example_webapp1), or customize the build for your standalone Form based App (like example_timeclock or form2) using the Standalone Options:

Standalone Options


Standalone App

From v9.5, checking the Standalone App setting (in Standalone Options shown in the screenshot above) will generate a complete standalone App. This can be used for all Windows desktop Apps, not just those based on a custom coded or autogenerated Form. See the example_webapp1 sample App.

Standalone Lib

From v9.5, checking the Standalone Lib setting (in Standalone Options shown in the screenshot above) will include library files. When the standalone app is run the library directory is set to the standalone App library directory.

The following text files are used to specify which library files should be included:

dependencies.txt

This is generated automatically when the App is built if the App contains any WebView Based Sections.

For example, the form2 App, includes the following sections:

and its dependencies.txt is generated as:

@articleview
@catalogview
@commentsview
@carouselview
@calendar

The '@sectiontype' format references the corresponding 'sectiontype-dependencies.txt' file in the library containing the names of any required files and directories, e.g. catalogview-dependencies.txt:

bootstrap-3.3.4
jquery-1.10.2
catalogview.*

includes.txt

The includes.txt file is used to specify any additional files that should be included.

As in dependencies.txt these can be:

  • folder names
  • individual file names
  • wildcards (like catalogview.*)
  • @filename to include the contents of '@filename-dependencies.txt'

Standalone Zip

From v9.5, checking the Standalone Zip setting (in Standalone Options shown in the screenshot above) will create a zip file for the standalone App.

Standalone Exe

From v9.5, checking the Standalone Exe setting (in Standalone Options shown in the screenshot above) will create an installer as a self-extracting exe file for the standalone App. This can be unzipped / installed on a target machine.

Standalone Login

From v9.5, checking the Standalone Login setting (in Standalone Options shown in the screenshot above) will include the files required for enabling login.

Here showing the login page for the example_timeclock App:

Standalone Login Page


Custom Coded Forms

To require login for access to the App, set the Form.requireslogin property to true:

local oform = createObject("Form")
oform.requireslogin = 1

The login page can optionally be customized using the Form.parameters property, e.g.:

oform.parameters = "login.caption=Factory Time Clock;";
+ "login._background=darkred;";
+ "login.backgroundimage=img/wallpapers/background.png;";
+ "login.version=Time Clock Version 1.0;";
+ "login.copyright=Copyright (c) (2024) LianjaDev. All Rights Reserved Worldwide."

Autogenerated and Non Form Based Apps

Login requirement is determined by the Standalone Login App Setting.

For non Form based Apps this can be overridden by the Enable guest access App Setting.

Specify login page customizations in the app.conf file, e.g. from example_webapp1:

login.title={lianja.apptitle}
login.headervisible=true
login.headericon=lib:/icons/ipad4.png
login.headercaption=Welcome to the {lianja.apptitle}
login.caption={lianja.apptitle}
login.background=#333
login.backgroundimage=app:/login.jpg
login.version={lianja.apptitle} Version {lianja.appversion}
login.copyright=Copyright (c) (2024) {lianja.appauthor}. All Rights Reserved Worldwide.

Note that you can use {macros} and lib:/ and app:/ special file prefixes in the app.conf file.

Standalone Python modules

From v9.5, checking the Standalone Python modules setting (in Standalone Options shown in the screenshot above) indicates that the standalone App requires Python modules in the App.

Standalone Data

From v9.5, specifying a comma separated list of databases in the Standalone Data setting (in Standalone Options shown in the screenshot above) will copy the databases into the standalone App directory. When the standalone app is run the data directory is set to the standalone App data directory.

Bm-noteicon.png
Remember:

Do not include the database(s) if your are building an update installer,
otherwise you will overwrite the existing data.
If you need to make data changes, code them in the setup script.
This is run before the App is loaded.

Additional Files

From v9.5, specifying a comma separated list of additional files in the Additional Files setting (in Standalone Options shown in the screenshot above) will copy them into the standalone App directory structure. Wildcards and the lib:/ special prefix may be used.

Setup

From v9.5, if the file setup.prg (or setup.py or setup.js) is contained within the App it is executed before the standalone App is run. You can check to see if the App is running standalone using Lianja.standalone and check the file path using Lianja.standalonefilepath.

You would typically do nothing in setup if the App is not running standalone.

// this is called when the app runs to setup databases, tables etc.
if not Lianja.standalone
	return
endif
 
set debugout on
debugout "running setup.prg"
debugout "filepath: "+Lianja.standalonefilepath
 
if not databaseExists("factory")
	debugout "database factory does not exist"
	create database factory
	try
		open database factory
		create table employees;
		 (badgeno char(30),;
		  firstname char(30),;
		  lastname char(30),;
		  photo blob)
		insert into employees (badgeno, firstname, lastname);
		 values ("A1234", "John", "Doe")
		create table timeclock;
		 (badgeno char(30),;
		  clockin datetime,;
		  clockout datetime,;
		  clockinphoto blob,;
		  clockoutphoto blob)
	catch
		debugout "An error occurred"
	endtry
endif
 
close database 
 
debugout "setup completed successfully"

Script

For custom coded Form based Apps the 'main' script must have the same name as the App. In the case of the sample example_timeclock App, this is example_timeclock.prg or example_timeclock.js or example_timeclock.py. You can switch the Scripting language in the App Settings to test each one.

LianjaScript/Visual FoxPro

Note the READ EVENTS at the end of the code which is needed for a standalone App.

example_timeclock.prg

//
// example_timeclock App
//
// When this app runs as a standalone app the setup.prg script is run 
// to setup datadir, databases, tables etc.
//
 
//
// database "factory" and its tables are created in setup.prg if they don't exist
//
// optional to share data between Windows and Linux: set samba on
//
 
open database factory 
local oform = createObject("Form")
Lianja.showSplashScreenMessage("Loading...")
 
// Standalone Apps can optionally require login
oform.requireslogin = 1
 
// You can customize the login page using form "parameters"
oform.parameters = "login.caption=Factory Time Clock;login._background=darkred;";
+ "login.backgroundimage=img/wallpapers/background.png;";
+ "login.version=Time Clock Version 1.0;";
+ "login.copyright=Copyright (c) (2024) LianjaDev. All Rights Reserved Worldwide."
 
oform.resize(700, 600)
oform.closable = 1
oform.caption = "Factory Time clock"
oform.minwidth = 700
oform.minheight = 600
 
// clicking the window "close" button will exit the standalone App
oform.exitonclose = 1
 
// move the window to the top of the screen beneath the camera
oform.move(screenWidth() / 2 - 700 / 2, 20)
 
oform.addObject("ocont", "container")
ocont.margin = 10
ocont.autosize = 1
ocont.layout = "V"
ocont.backcolor = "black"
 
ocont.addObject("obadgenolab", "label")
obadgenolab.fixedheight = 24
obadgenolab.autowidth = 1
obadgenolab.alignment = "center"
obadgenolab.caption = "Enter Badge Number"
obadgenolab.backcolor = "darkgray"
obadgenolab.forecolor = "white"
obadgenolab.fontbold = 1
obadgenolab.fontsize = 10
 
ocont.addspacing(10)
ocont.addObject("obadgeno", "textbox")
obadgeno.fixedheight = 24
obadgeno.autowidth = 1
obadgeno.inputmask = "@!"
obadgeno.placeholder = "Enter your badge number, then CLOCK IN or CLOCK OUT"
 
ocont.addspacing(10)
ocont.addObject("obadgenolab2", "label")
obadgenolab2.fixedheight = 24
obadgenolab2.autowidth = 1
obadgenolab2.alignment = "center"
obadgenolab2.caption = "Take a Photo"
obadgenolab2.backcolor = "darkgray"
obadgenolab2.forecolor = "white"
obadgenolab2.fontbold = 1
obadgenolab2.fontsize = 10
 
Lianja.showSplashScreenMessage("Searching for cameras")
ocont.addspacing(10)
ocont.addObject("ocamera", "camera")
ocamera.autowidth = 1
ocamera.autoheight = 1
ocamera.backcolor = "black"
Lianja.showSplashScreenMessage("Loading...")
 
ocont.addspacing(10)
ocont.addObject("obadgenolab3", "label")
obadgenolab3.fixedheight = 24
obadgenolab3.autowidth = 1
obadgenolab3.alignment = "center"
obadgenolab3.caption = "Clock in / Clock out"
obadgenolab3.backcolor = "darkgray"
obadgenolab3.forecolor = "white"
obadgenolab3.fontbold = 1
obadgenolab3.fontsize = 10
 
ocont.addObject("ocont2", "container")
ocont2.layout = "H"
ocont2.fixedheight = 60
ocont2.backcolor = "black"
ocont2.spacing = 10
 
ocont2.addObject("obtn1", "commandbutton")  
obtn1.resize(60, 50)
obtn1.flat = 1
obtn1.fixedheight = 50
obtn1.caption = "CLOCK" + chr(10) + "IN"
obtn1.stylesheet = "btn"
 
ocont2.addObject("obtn2", "commandbutton")  
obtn2.flat = 1
obtn2.resize(60, 50)
obtn2.fixedheight = 50
obtn2.stylesheet = "btn"
obtn2.caption = "CLOCK" + chr(10) + "OUT" 
 
ocont2.addObject("obtn3", "commandbutton")  
obtn3.flat = 1
obtn3.resize(60, 50)
obtn3.fixedheight = 50
obtn3.stylesheet = "btn"
obtn3.caption = "MANAGE" + chr(10) + "CLOCK"
 
ocont2.addObject("obtn4", "commandbutton")  
obtn4.flat = 1
obtn4.resize(60, 50)
obtn4.fixedheight = 50
obtn4.stylesheet = "btn"
obtn4.caption = "MANAGE" + chr(10) + "EMPLOYEES"
 
ocont2.addObject("obtn5", "commandbutton")  
obtn5.flat = 1
obtn5.resize(60, 50)
obtn5.fixedheight = 50
obtn5.stylesheet = "btn"
obtn5.caption = "LOGOUT" 
 
ocont2.addObject("ocntin", "label")  
ocntin.resize(60, 50)
ocntin.fixedheight = 50
ocntin.alignment = "center"
ocntin.fontsize = 16
ocntin.backcolor = "black"
ocntin.forecolor = "white"
ocntin.caption = "Clocked In<br>0" 
 
ocont2.addObject("ocntout", "label")  
ocntout.resize(60, 50)
ocntout.fixedheight = 50
ocntout.alignment = "center"
ocntout.fontsize = 16
ocntout.backcolor = "black"
ocntout.forecolor = "white"
ocntout.caption = "Clocked Out<br>0" 
 
proc updateCounts()
	m_cnt = sqlEval("select cnt(*) from timeclock where empty(clockout)")
	ocntin.caption = "Clocked In<br>" + etos(m_cnt)
	m_cnt = sqlEval("select cnt(*) from timeclock where not empty(clockout)")
	ocntout.caption = "Clocked Out<br>" + etos(m_cnt)
endproc
 
// event handlers
proc clockin()
	local cBadgeno = alltrim(obadgeno.text)
	if len(alltrim(cBadgeno)) = 0
		thisform.showWarningMessage("Please must specify your badge number")
		return
	endif
	local m_cnt = sqlEval("select cnt(*) from employees where badgeno='&cBadgeno'")
	if etos(m_cnt) = "0"
		thisform.showWarningMessage("Unrecognized badge number!")
		return
	endif
	local m_clockout = sqlEval("select clockout from timeclock where badgeno='&cBadgeno' and empty(clockout)")
	if len(alltrim(m_clockout)) > 0
		thisform.showWarningMessage("You must clock out before you can clock in again!")
		return
	endif
	obadgeno.text = ""
	local cPhoto = ocamera.takePhoto()
	insert into timeclock (badgeno, clockin, clockinphoto) values (cBadgeno, datetime(), cPhoto)
	if _tally = 1
		thisform.showSuccessMessage("You clocked in on " + dtoc(date()) + " at " + ampm())
	else
		thisform.showErrorMessage("Failed to clock in")
	endif
	updateCounts()
endproc
 
proc clockout()
	local cBadgeno = alltrim(obadgeno.text)
	if len(alltrim(cBadgeno)) = 0
		thisform.showWarningMessage("Please must specify your badge number")
		return
	endif
	local m_cnt = sqlEval("select cnt(*) from employees where badgeno='&cBadgeno'")
	if etos(m_cnt) = "0"
		thisform.showWarningMessage("Unrecognized badge number!")
		return
	endif
	obadgeno.text = ""
	local m_cnt = sqlEval("select cnt(*) from timeclock where badgeno='&cBadgeno' and empty(clockout)")
	if etos(m_cnt) = "0"
		thisform.showWarningMessage("You must clock in before you can clock out!")
		return
	endif
	local cPhoto = ocamera.takePhoto()
	update timeclock set clockout = datetime(), clockoutphoto = "&cPhoto" where badgeno='&cBadgeno' and empty(clockout)
	if _tally = 1
		thisform.showSuccessMessage("You clocked out on " + dtoc(date()) + " at " + ampm())
	else
		thisform.showErrorMessage("Failed to clock out")
	endif
	updateCounts()
endproc
 
proc admin()
	use timeclock
	browse splitbar ;
		fields badgeno:h="Badge Number", clockin:h="Clock In Time", clockout:h="Clock Out Time" ; 
		properties "splitimagelist=clockinphoto:Clock In Photo,clockoutphoto:Clock Out Photo" ;
		title "Manage Clock"
	use
	updateCounts()
endproc
 
proc employees()
	use employees
	browse splitbar ;
		fields badgeno:h="Badge Number", firstname:h="First Name", lastname:h="Last Name" ;
		properties "splitimagelist=photo:Photo" ;
		title "Manage Employees"
	use
	updateCounts()
endproc
 
proc afterlogin()
	if username() != "admin"
		obtn3.hide()
		obtn4.hide()
		obtn5.hide()
		ocntin.hide()
		ocntout.hide()
	else
		obtn3.show()
		obtn4.show()
		obtn5.show()
		ocntin.show()
		ocntout.show()		
	endif
endproc
 
proc logout()
	thisform.logout()
endproc
 
// Assign event handlers for the events on UI controls
oform.afterlogin = afterlogin
obtn1.click = clockin
obtn2.click = clockout
obtn3.click = admin
obtn4.click = employees
obtn5.click = logout
 
// Optionally enable debug on the form at runtime
// oform.debug()
 
// modal form
oform.show(1)
 
// must put read events at the bottom so the window stays open until closed by user
read events

Python

  • Check that pip is the latest version. From a Windows Command Prompt (replace drive: with the drive for your Lianja App Builder installation):
cd drive:\lianja\bin
python -m pip install --upgrade pip --no-warn-script-location
  • In the Lianja App Builder Console Workspace, create the python3 directory to contain the modules local to the App (this has already been created for the example_timeclock App):
mkdir python3
  • Install local Python modules (pip can be run from the Lianja/VFP tab in the Console Workspace Command Window), e.g. numpy:
pip install --target=drive:\lianja\apps\example_timeclock\python3\ numpy

Note: the local installation of the Python modules causes these modules to be included when the standalone executable is generated. During App development, any required modules also need to be installed in the default location for Lianja, i.e. without a --target specified.

To check modules installed in the default location:

pip list

To check the Python path (from the Python tab):

import sys
print(sys.path)

example_timeclock.py

import sys
import types
from datetime import datetime
import Lianja
 
class myCommandbutton(Lianja.Commandbutton):
	pass
 
oform = createObject("form")
oform.resize(600, 600)
oform.closable = 1
oform.caption = "Time clock - Python"
oform.minwidth = 600
oform.minheight = 600
 
oform.addObject("ocont", "container")
ocont.margin = 10
ocont.autosize = 1
ocont.layout = "V"
ocont.backcolor = "black"
 
ocont.addObject("obadgenolab", "label")
obadgenolab.fixedheight = 24
obadgenolab.autowidth = 1
obadgenolab.alignment = "center"
obadgenolab.caption = "Enter Badge Number"
obadgenolab.backcolor = "darkgray"
obadgenolab.forecolor = "white"
obadgenolab.fontbold = 1
obadgenolab.fontsize = 10
 
ocont.addspacing(10)
ocont.addObject("obadgeno", "textbox")
obadgeno.fixedheight = 24
obadgeno.autowidth = 1
obadgeno.placeholder = "Enter your badge number, Take a Photo then CLOCK IN or CLOCK OUT"
 
ocont.addspacing(10)
ocont.addObject("obadgenolab2", "label")
obadgenolab2.fixedheight = 24
obadgenolab2.autowidth = 1
obadgenolab2.alignment = "center"
obadgenolab2.caption = "Take a Photo"
obadgenolab2.backcolor = "darkgray"
obadgenolab2.forecolor = "white"
obadgenolab2.fontbold = 1
obadgenolab2.fontsize = 10
 
ocont.addspacing(10)
ocont.addObject("ocamera", "camera") 
ocamera.autowidth = 1
ocamera.autoheight = 1
ocamera.backcolor = "black"
 
ocont.addspacing(10)
ocont.addObject("obadgenolab3", "label")
obadgenolab3.fixedheight = 24
obadgenolab3.autowidth = 1
obadgenolab3.alignment = "center"
obadgenolab3.caption = "Clock in / Clock out"
obadgenolab3.backcolor = "darkgray"
obadgenolab3.forecolor = "white"
obadgenolab3.fontbold = 1
obadgenolab3.fontsize = 10
 
ocont.addObject("ocont2", "container")
ocont2.layout = "H"
ocont2.fixedheight = 60
ocont2.backcolor = "black"
ocont2.spacing = 10
 
ocont2.addObject("obtn1", "myCommandbutton")  
obtn1.resize(60, 50)
obtn1.flat = 1
obtn1.fixedheight = 50
obtn1.caption = "CLOCK IN"
obtn1.stylesheet = "btn"
ocont2.addObject("obtn2", "myCommandbutton")  
obtn2.flat = 1
obtn2.resize(60, 50)
obtn2.fixedheight = 50
obtn2.stylesheet = "btn"
obtn2.caption = "CLOCK OUT" 
 
# event handlers
def clockin(self):
	ocamera.takePhoto()
	now = datetime.now()
	oform.showSuccessMessage("You clocked in at " + now.strftime("%m/%d/%Y %I:%M:%S %p"))
 
def clockout(self):
	ocamera.takePhoto()
	now = datetime.now()
	oform.showSuccessMessage("You clocked out at " + now.strftime("%m/%d/%Y %I:%M:%S %p"))
 
# We can dynamically bind events to custom classes in Lianja/Python
bindEvent(obtn1, clockin, "click")
bindEvent(obtn2, clockout, "click")
 
# modal form
oform.show()
Lianja.readEvents()

JavaScript

example_timeclock.js

var oform = createObject("Form");
oform.resize(600, 600);
oform.closable = 1;
oform.caption = "Time clock - JavaScript";
oform.minwidth = 600;
oform.minheight = 600;
oform.deleteonclose = 1;
 
oform.addobject("ocont", "container"); 
ocont.margin = 10;
ocont.autosize = 1;
ocont.layout = "V";
ocont.backcolor = "black";
 
ocont.addObject("obadgenolab", "label"); 
obadgenolab.fixedheight = 24;
obadgenolab.autowidth = 1;
obadgenolab.alignment = "center";
obadgenolab.caption = "Enter Badge Number";
obadgenolab.backcolor = "darkgray";
obadgenolab.forecolor = "white";
obadgenolab.fontbold = 1;
obadgenolab.fontsize = 10;
 
ocont.addspacing(10);
ocont.addObject("obadgeno", "textbox");
obadgeno.fixedheight = 24;
obadgeno.autowidth = 1;
obadgeno.placeholder = "Enter your badge number, Take a Photo then CLOCK IN or CLOCK OUT";
 
ocont.addspacing(10);
ocont.addObject("obadgenolab2", "label");
obadgenolab2.fixedheight = 24;
obadgenolab2.autowidth = 1;
obadgenolab2.alignment = "center";
obadgenolab2.caption = "Take a Photo";
obadgenolab2.backcolor = "darkgray";
obadgenolab2.forecolor = "white";
obadgenolab2.fontbold = 1;
obadgenolab2.fontsize = 10;
 
ocont.addspacing(10);
ocont.addObject("ocamera", "camera");
ocamera.autowidth = 1;
ocamera.autoheight = 1;
ocamera.backcolor = "black";
 
ocont.addspacing(10);
ocont.addObject("obadgenolab3", "label");
obadgenolab3.fixedheight = 24;
obadgenolab3.autowidth = 1;
obadgenolab3.alignment = "center";
obadgenolab3.caption = "Clock in / Clock out";
obadgenolab3.backcolor = "darkgray";
obadgenolab3.forecolor = "white";
obadgenolab3.fontbold = 1;
obadgenolab3.fontsize = 10;
 
ocont.addObject("ocont2", "container");
ocont2.layout = "H";
ocont2.fixedheight = 60;
ocont2.backcolor = "black";
ocont2.spacing = 10;
 
ocont2.addObject("obtn1", "commandbutton");  
obtn1.resize(60, 50);
obtn1.flat = 1;
obtn1.fixedheight = 50;
obtn1.caption = "CLOCK IN";
obtn1.stylesheet = "btn";
ocont2.addObject("obtn2", "commandbutton"); 
obtn2.flat = 1;
obtn2.resize(60, 50);
obtn2.fixedheight = 50;
obtn2.stylesheet = "btn";
obtn2.caption = "CLOCK OUT";  
 
// event handlers
function clockin() 
{
	ocamera.takePhoto()
	oform.showSuccessMessage("You clocked in on " + new Date().toLocaleString())
};
 
function clockout()
{
	ocamera.takePhoto();
	oform.showSuccessMessage("You clocked out on " + new Date().toLocaleString())
};
 
obtn1.click = clockin;
obtn2.click = clockout;
 
// modal form
oform.show();
 
// must put read events at the bottom so the window stays open until closed by user
read_events();

app.conf

An app.conf text file can be created in your App to list key/value pairs. These can be used to customize the login page as shown above and/or to provide additional values that your App can reference.

Use the Lianja.getParameter() system method in your code, specifying the key (cKey) and a default value (cDefault) if the key is not defined:

myvar = Lianja.getParameter(cKey,cDefault)

Splashscreen

If your App includes an image file named splashscreen.png, it will be displayed as a splashscreen before the App loads.

Splashscreen


Building and Testing Standalone Executables

To build and test click the 'Desktop App View' button in the Headerbar.

The Output window will show the compilation and build operations.

Here showing the login page after building the example_timeclock App:

Sample App: example_timeclock


To build and test the other scripts in the example_timeclock App - LianjaScript (.prg), Python (.py) and JavaScript (.js) are available - just select the required scripting language in the App Settings and click the 'Desktop App View' button again.

Lianja Standalone App Debugger

From v9.5 standalone Apps can be debugged at runtime.

Debugger


Starting the Debugger

For custom coded Forms (example_timeclock), start the debugger using the form.debug() method in the code.

Autogenerated Forms (form2) and non Form based Apps (example_webapp1) automatically show the debugger when built in Debug mode. The Modebar Selector can be used to toggle between Release and Debug mode.

For non Form based Apps (example_webapp1), you can also call the Lianja.debug() method in your code when running in Release mode.

Suspend

For custom coded Forms (example_timeclock), suspend execution using the form.suspend() method.

For autogenerated Forms (form2), call the Lianja.suspend() method in the debugger 'Input' panel.

For non Form based Apps (example_webapp1), call the Lianja.suspend() method in your code.

Input Panel

Type commands in the 'Input' panel, e.g.

list status

Output Panel

View the stack trace and command output in the 'Output' panel.

The set debugout and debugout commands can also be included in code to display 'debugout' text in the 'Output' panel. See the setup.prg above for an example of this.

Resume

Click the 'Run' button in the debugger to resume execution.

Alternatively, call the Lianja.resume() method.

Deploying Standalone Executables

When building a standalone executable with LianjaScript, all the .prg files in the App directory are linked together into a .src file and this is compiled.

The resulting object file is bound onto the end the Lianja runtime executable and the authenticode code signing is adjusted so as to not be affected by the application payload.

All of the supporting libraries are included in the standalone application directory.

If your application is built in Python, there is a python3 directory included which contains the python modules you have installed with pip.

Pre-9.5 Deployment

To deploy, install the whole standalone application directory, e.g for the example_timeclock App the directory is:
drive:\lianja\installers\standalone\example_timeclock

and it has a single bin sub-directory containing the executable and required libraries:

drive:\lianja\installers\standalone\example_timeclock\bin

To run, the executable is:

drive:\lianja\installers\standalone\example_timeclock\bin\example_timeclock.exe

Note: In v7.0, the exe and required libraries are located in the application directory itself, e.g. example_timeclock (this has been corrected in v7.1). These need to be placed in a bin directory, either by renaming the directory to bin or by creating a bin sub-directory and copying all the other files into the new sub-directory.

Post-9.5 Deployment

From v9.5 you can also choose to deploy your App in the following ways: