Here is an overview of how the documentation is organized, that will help you know where to look for certain things:
Getting started topics describe how to install the framework, create a new project, take you through a series of steps to develop a Web application and explains how to deploy it.
Programming guides discuss key topics and concepts at a fairly high level and provide useful background information and explanation.
Business application builder is a detailed description of the Application Builder used for application development and database administration.
Class reference guides contain technical reference for Jam.py classes APIs
FAQ topics covers most frequently asked questions.
How to contains code examples that can be useful to quickly accomplish common tasks
Or you can go to the table of contents
If you are new to Jam.py, we highly recommend that you watch these video tutorials
It is recommended to watch these videos with a resolution of 1080p
Tutorial 1 - Working with files and images
Tutorial 2 - Working with details
Tutorial 3 - Users, roles, audit trail/change history
Tutorial 4 - Task tree
Tutorial 5 - Forms
Tutorial 6 - Form events
Tutorial 7 - Data aware controls
Tutorial 8 - Datasets
Tutorial 9 - Datasets Part 2
Tutorial 10 - Fields and filters
Tutorial 11 - Client-server interactions
Tutorial 12 - Working with data on the server
Here you can learn how to install the framework, create a new project, develop a web application and deploy it.
Jam.py requires python. If it is not installed you can get the latest version of Python at https://www.python.org/download/
You can use the following versions of Python with Jam.py:
Python 2
Python 3
You can verify that Python is installed by typing python from your shell; you should see something like:
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
If Python 2 and Python 3 are installed try to type python3:
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
This is the recommended way to install Jam.py.
Install pip. The easiest is to use the standalone pip installer. If your
distribution already has pip
installed, you might need to update it if
it’s outdated. (If it’s outdated, you’ll know because installation won’t
work.)
If you’re using Linux, Mac OS X or some other flavor of Unix, enter the command
sudo pip install jam.py
at the shell prompt.
If you’re using Windows, start a command shell with administrator privileges and run the command
pip install jam.py
This will install Jam.py in your Python installation’s site-packages
directory.
$ python setup.py install
This will install Jam.py in your Python installation’s site-packages directory.
Note
on some unix like systems you may need to switch to root or run: sudo python setup.py install
It is best practice to provide a dedicated environment for each Jam.py project you create. There are many options to manage environments and packages within the Python ecosystem, some of which are recommended in the Python documentation.
To create a virtual environment for your project, open a new command prompt, navigate to the folder where you want to create your project and then enter the following:
...\> py -m venv project-name
This will create a folder called ‘project-name’ if it does not already exist and set up the virtual environment. To activate the environment, run:
...\> project-name\Scripts\activate.bat
The virtual environment will be activated and you’ll see “(project-name)” next to the command prompt to designate that. Each time you start a new command prompt, you’ll need to activate the environment again.
Jam.py can be installed easily using pip
within your virtual environment.
In the command prompt, ensure your virtual environment is active, and execute the following command:
...\> py -m pip install jam.py
This will download and install the latest Jam.py release.
After the installation has completed, you can verify your Jam.py installation
by executing pip list
in the command prompt.
If you are connecting to the internet behind a proxy, there might be problems
in running the command py -m pip install Jam.py
. Set the environment
variables for proxy configuration in the command prompt as follows:
...\> set http_proxy=http://username:password@proxyserver:proxyport
...\> set https_proxy=https://username:password@proxyserver:proxyport
If your Administrator prohibited setting up a virtual environment, it is still possible to install Jam.py as follows:
...\> python -m pip install jam.py
This will download and install the latest Jam.py release.
After the installation has completed, you can verify your Jam.py installation
by executing pip list
in the command prompt.
However, running jam-project.py
will fail since it is not in the path. Check
the installation folder:
...\> python -m site --user-site
The output might be similar to below:
C:\Users\youruser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages
Replace site-packages
at the end of above line with Scripts
:
...\> dir C:\Users\youruser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\Scripts
The output might be similar to below:
...\> Directory of C:\Users\yourser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\Scripts
13/04/2023 02:59 PM <DIR> .
13/04/2023 02:59 PM <DIR> ..
13/04/2023 02:59 PM 1,087 jam-project.py
1 File(s) 1,087 bytes
2 Dir(s) 177,027,321,856 bytes free
Create the new folder somewhere and run jam-project
from from it:
...\> python C:\Users\youruser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\Scripts\jam-project.py
Run the new project:
...\> python server.py
Create a new directory.
Go into the directory and run from command line:
$ jam-project.py
The following files and folders will be created in the directory:
/
css/
js/
img/
reports/
static/
admin.sqlite
server.py
index.html
wsgi.py
To start Jam.py web server run server.py script.
$ ./server.py
Note
You can specify a port as parameter, for example
$ ./server.py 8081.
By default, the port is 8080. If you will specify another port, you need to use it in a browser in the next steps.
Open a Web browser and go to “/builder.html” on your local domain – e.g., http://127.0.0.1:8080/builder.html. You should see the select language dialog.
127.0.0.1:8080/builder.html
In the dialog that will appear, select the language and press OK button.
In the New project dialog box fill in:
When you press OK, the connection to the database will be checked, and in case of failure an error message will be displayed.
Note
Please note the following requirements:
fdb
library must be installedpsycopg2
libraryMySQLdb
librarycx_Oracle
librarypymssql
libraryNote
For SQLite database, when an item field is deleted or renamed, or foreign key is created, Application builder, creates a new table and copies records from the old one into it.
For SQLite database, Jam.py doesn’t support importing of metadata into an existing project (project with tables in the database). You can only import metadata into a new project.
If all goes well, a new project will be created and the project tree will appear in the Application builder.
Now, to see the project itself, create a new page in the browser and type in the address bar:
127.0.0.1:8080
The framework has a full fledged demo application that demonstrates programming techniques used in the framework.
The demo is located in the demo folder of the Jam.py package or you can download it by clicking on the link.
To start the demo application go to the demo folder and run server.py script.
$ ./server.py
Open a Web browser and enter 127.0.0.1:8080 in the address bar.
To see Application builder open a new page in a browser and enter 127.0.0.1:8080/builder.html
Now, we’ll walk you through the creation of a basic CRM application. Please follow the steps below:
We’ll assume that jam.py is already installed. If not, see Installation guide how to do it.
First we create a folder for the new project and in this folder we execute the jam-project.py script to create the project structures.
$ jam-project.py
After that we run server.py script that jam-project.py have created:
$ ./server.py
Now, to complete the creation of the project, open the web browser and go to 127.0.0.1:8080/builder.html to open the Application Builder. You should see the language selection dialog.
Select English and click OK button. The project parameters dialog box appears.
Fill out the form and click “OK”. Now you should see the project tree in the left panel.
Open a new page, type 127.0.0.1:8080 in the address bar and press Enter. A new project appears with an empty menu.
Let’s go back to the Application builder page and create a “Customers” catalog.
Now we select the “Catalogs” group in the project tree and and click the New button at the bottom right corner of the page
In the Item Editor dialog, fill in the caption and name of the new catalog
and click the New button in the bottom right corner of the dialog to add a new field. The Field Editor dialog appears. Type the caption and name of the “Firstname” field, select its type and click OK button.
Similarly, we added the “Lastname” and “Phone” fields. When adding the “Lastname” field, we checked the Required attribute.
Now, to save the changes, click the OK button. When saving, the Application builder created the CRM_CUSTOMERS table in the ctm.sqlite database:
Go to the Project page, refresh it and click the New button and then OK button:
Now we will create the “Contacts” item.
Select the “Journals” group in the project task tree and add a new journal in the same way that we created the “Customers” catalog.
First we add the “Contact date” field of the “datetime” type and then “Notes” fields of the “text” type.
Let’s add the lookup field “Customer” field that will store a reference to a record in the “Customers” catalog.
To create a lookup field, after specifying its caption and name, we need to select a lookup item. Select Lookup tab and click the button to the right of the Lookup item input
and double click the record to select it.
The same way specify a lookup field.
In the same way we add the “Firstname” and “Phone” lookup fields. For this fields we specify the “Customer” field as their Master field attribute.
Click the “OK” button to save the “Contacts” item.
As you can see, there are no “FIRSTNAME” and “PHONE” fields in the “CRM_CONTACTS” table. This is due to the fact that we have set Master field attribute of these fields to “Customer”. The “Customer” field will store a reference to a record in the “Customers” catalog and that record have the “Fisrtname” and “Phone” fields.
When we refresh the project page and click the New button we’ll see that there is a small button to the right of the “Customer” input.
When we click on it and select a record in the “Customers” catalog the fields “Customer”, “Firstname” and “Phone” will be filled.
Now we create a lookup List “Status”.
Select the “Task” node in the project tree and click the Lookup lists button.
Click the New button and specify the new lookup list name and add a list of integer-text pairs:
Save the Lookup Lists dialog and open the “Contacts” journal to add the “Status” field
and set the Lookup values attribute to the “Status” lookup list:
And finally, before saving, open the “Customer” field and set the Required and Typeahead attributes. When the Typeahead is checked, typeahead is enabled for the lookup field,
set Default value of the “Contact date” field to “CURRENT DATETIME”
and Default value of the “Status” field to “Contact” selecting them in the drop-down lists.
When we refresh the project page we’ll see that fields in the table and in the edit form of the “Contacts” journal are displayed in the order in which they were created.
To change how fields are displayed in the table, click the View Form button to open the View Form Dialog Let’s change the displayed fields using left, right, up and down buttons
Let’s click on the button right to the Sort fields input and select the fields by which user can sort the contents of the table by clicking in the corresponding column header of the table.
To change the way the fields are displayed in the edit form click the Edit Form button to open the Edit Form Dialog
To see the result of our work, go to the project page, refresh it and click the New button.
Let’s set the default sorting of records of the “Contacts” journal. To do so click the Order button:
And now we create a corresponding index for the “Contacts” journal database table. Click the Indices button to open Indices Dialog and then click the New button and specify the index:
Filters are used to select records from the database table according to the specified criteria.
Click the Filters button to open Filters Dialog
Now click the New button and fill out the following form:
Similarly, we created other filters:
When we refresh the project page, the Filters button will appear in the header of the “Contacts” form. Clicking this button opens the “Filters” dialog box:
In this part we will demonstrate how to work with files and images in Jam.py.
Let’s select the “Customers” catalog, Double-click it to open the Item Editor Dialog and add an image field “Photo”:
Now refresh the project page, click the Customers menu item and open the edit form.
Double-click the image in the editing form to select an image from the Open File dialog box.
Note
To clear an image, hold down the Ctrl key and double-click the image.
Let’s open the Field Editor Dialog in Application Builder and set View width to 120 and Edit width to 314 on the Interface tab.
Note
You can set the image placehodler by double-clicking on it.
In the View Form Dialog we set Row lines to 4 and the width of the “Photo” field to 120.
Now on the project page we will have:
You can capture the image from the camera. To do so check the Capture from camera check box. In this case when the image is not set the video from camera will be displayed instead of the image placeholder.
Double-click the video to capture the image. To clear an image, hold down the Ctrl key and double-click the image, after that the video will be displayed.
Now we add a field that will store an appendix file to the “Contacts” journal.
This field will be displayed in the editing form as follows:
The field input have three buttons on the right - to upload, to download and to open a file.
Let’s open the Field Editor Dialog in Application Builder and uncheck the Download btn check box and set Accept attribute to ‘.pdf’.
Let’s refresh the project page, open the “Contacts” edit form and upload a file by clicking the upload button:
Now we can open a file in the browser by clicking on the open button.
Note
Files and images are stored in the static/files folder on the server.
You can limit the size of files that can be uploaded to the server by setting Max content length attribute in the project parameters.
In this part of the tutorial we will explain how to work with details.
Let’s select the “Details” group in the project tree and and click the New button at the bottom right corner of the page
In the Item Editor dialog box, we will name the new item “To do list” and add the two fields “Created” and “To do” in the same way as in the previous tutorial:
After saving the “To do list”, select the “Contacts” journal and click the Details button in the right pane to open the Details Dialog. Click the right arrow button to add the “To do list” to the “Contacts” details and the OK button to save changes.
A new “To do list” item will be created as a child of the “Contacts” journal.
Select the “Contacts” journal again and click the Edit form button to open the Edit Form Dialog. Select Form tab, click the button to the right of the Edit details input and select the “To do list” check box.
Let’s update the project page and dblclick on the contact. Now we can add items to the to-do list of the contact.
Click the Groups node in the project tree, dblclick the Details row and set Visible attribute to true.
When we refresh the project page, we will see the “To do list” item in the main menu. Click on it to see the to do list of all contacts.
Select the “Contacts” journal again and click the View form button to open the View Form Dialog. Select Form tab, click the button to the right of the View detail input and select the “To do list” check box.
In the project page will see that the to-do list changes when the contact changes.
Once you’ve got mod_wsgi
installed and activated, edit your Apache server’s
httpd.conf file and add the following. If you are using a version of Apache older
than 2.4, replace Require all granted with Allow from all and also add
the line Order deny,allow above it.
WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py
WSGIPythonPath /path/to/mysite.com
<Directory /path/to/mysite.com/mysite>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
Alias /static/ /path/to/mysite.com/static/
<Directory /path/to/mysite.com/static>
Require all granted
</Directory>
The first bit in the WSGIScriptAlias
line is the base URL path you want to
serve your application at (/
indicates the root url), and the second is the
location of a “WSGI file” – see below – on your system, usually inside of
your project package (mysite
in this example). This tells Apache to serve
any request below the given URL using the WSGI application defined in that
file.
The WSGIPythonPath
line ensures that your project package is available for
import on the Python path; in other words, that import mysite
works.
The <Directory>
piece just ensures that Apache can access your
wsgi.py
file.
The next lines ensure that anything in the /static/
URL space is explicitly
served as a static files.
See the additional information on the deployment in the How to deploy
Here, the basic concepts of Jam.py programming will be explained.
All objects of the framework represent a tree of objects. These object are called items.
All items of the tree have common ancestor class AbstractItem
(
client reference
/
server reference
) and common attributes:.
ID
- unique in the framework ID of the itemowner
- immediate parent and owner of the itemtask
- root of the task treeitems
- list of child itemsitem_type
- type of the itemitem_name
- the name of the item that will be used in programming code
to get access to the itemitem_caption
- the item name that appears to usersAt the root of the tree is the task item.
The task contains group items. There are three types of groups that have the
following values of the item_type
attribute:
item_type
, that can have
associated database table.item_type
,
that are used to create reports.You can create your own groups.
Items that can have associated database table can own details, that are used to store records that belong to a record of the master.
For example the task tree of the Demo project is:
/demo/
catalogs/
customers
tracks
albums
artists
genres
media_types
journals/
invoices/
invoice_table
details/
invoice_table
reports/
invoice
purchases_report
customers_report
At the root of the task tree is a task with the item_name
demo. It has
four groups: catalogs, journals, details and reports. The
catalogs, journals groups have item_type
“items”. The items they own
are wrappers over the corresponding database tables. There is one detail item with
item_name
invoice_table, that also has it’s own database table, and three
reports in the reports group.
The invoices journal has the invoice_table detail, which keeps a list of tracks in an customer’s invoice. So there are two items with the same name “invoice_table” (detail_item and detail). They share the same database table.
Every item is an attribute of its owner and all items, tables and reports are
attributes the task as well (they all have a unique item_name
).
A task is a global object on the client. To access it, just type task
anywhere in the code.
On the server, the task is not global. Jam.py is an event-driven environment. Each event has as a parameter the item (or field) that triggered the event. Functions defined in the server module of an item that can be executed from the client module using the server method have the corresponding item as the first parameter as well.
Knowing an item, we can access any other item of the task tree. For example to get access to the customers catalog we can write
def on_apply(item, delta, params):
customers = item.task.catalogs.customers.copy()
or just
def on_apply(item, delta, params):
customers = item.task.customers.copy()
The hierarchical structure of the project is one of the bases of the DRY (don’t repeat yourself) principle of the framework.
For example, some methods of the items, when executed, successively generate events for the task, group and the item.
This way we can define a basic behavior for all items in the event handler of the task, that can be expanded in the event handler of the group, and finally, if necessary, can be specified in the event handler of the item itself. For more details see Form events
The Task tree video tutorial demonstrates the task tree using Demo project
In the Jam.py framework, two tasks work at the same time: the Application builder and the Project. Each of them represents a tree of objects - there is the Application builder task tree and the Project task tree. Therefore, before considering the Jam.py workflow, you need to familiarize yourself with the concept of the task tree.
The the Jam.py workflow is the following:
builder.html
or index.html
have been loaded) the
empty task object is built that sends to the server a request to initialize
itself.index.html
file.
In these functions a developer can use methods of items of the
task tree
to perform some specific tasks.
These methods, when executed, trigger different events in which other methods
could be called and so on. See
Client side programming.Form events and Client-server interactions video tutorials illustrate the workflow of Jam.py project.
For every item of the project task tree a developer can write code that will be executed on the client or server. In Application builder for every item there is two upper-right buttons Client module and Server module. Clicking on these will open the code editor.
Every item has a predefined set of events that could be triggered by application. An event is a function defined in the module of an item that starts with the on_ prefix. All published events are listed in the Events tab of the information pane of the code editor
In the code editor the developer can write code for these events as well as define some functions.
For example the following code means that immediately after adding a new record to the Invoices journal of the Demo project, the value of the invoicedate field will be equal to the current date.
function on_after_append(item) {
item.invoicedate.value = new Date();
}
Note
These events and functions became attributes of the item and could be accessed anywhere in the project code.
For example, the following code defined in the item client module will execute on_edit_form_created event handler defined in the Customers item for this item.
function on_edit_form_created(item) {
task.customers.on_edit_form_created(item);
}
When user opens a Jam.py application in a Web browser, the browser first loads the index.html file. This file is located in the root directory of a project.
It is the usual html file containing links to css and js files, that client application is using. The files that start with jam are located in the jam folder of the Jam.py package directory on the server.
For example
<link href="jam/css/jam.css" rel="stylesheet">
If needed, other files can be added here. For example some charting library. It is better to place them in the js and css folders of the static directory of the project.
For example
<script src="static/js/Chart.min.js"></script>
The index.html body
tag have a div
with class templates
, that
contains html templates of the project forms. See
Forms
and
Form templates.
for details.
At the end of the file there is a following script:
<script>
$(document).ready(function(){
task.load()
});
</script>
In this script the load method of the task, that has been created when jam.js file was loaded, is called that loads information about the task tree from the server and, based on this information, builds its tree, loads modules, assigns event handlers to its items and triggers on_page_loaded event. See Initializing application
The on_page_loaded event is the first event triggered by an application on the client.
The new project uses on_page_loaded event handler to dynamically build the application’s main menu and attach the on click event handler to menu items using JQuery.
function on_page_loaded(task) {
$("title").text(task.item_caption);
$("#title").text(task.item_caption);
if (task.safe_mode) {
$("#user-info").text(task.user_info.role_name + ' ' + task.user_info.user_name);
$('#log-out')
.show()
.click(function(e) {
e.preventDefault();
task.logout();
});
}
if (task.full_width) {
$('#container').removeClass('container').addClass('container-fluid');
}
$('#container').show();
task.create_menu($("#menu"), $("#content"), {view_first: true});
}
This event handler uses JQuery to select elements from the index.html to set their attributes and assign events.
<div id="container" class="container" style="display: none">
<div class="row-fluid">
<div class="span6">
<h3 id="title" class="muted"></h3>
</div>
<div class="span6 logging-info">
<span id="user-info"></span>
<a id="log-out" href="#" style="display: none">Log out</a>
</div>
</div>
<div class="container">
<div id="taskmenu" class="navbar">
<div class="navbar-inner">
<ul id="menu" class="nav">
</ul>
</div>
</div>
</div>
<div id="content">
</div>
</div>
Finally, the create_menu method of the task is called to dynamically create the main project menu.
One of the key concepts of the framework is the concept of form.
When the user clicks the menu item of the main menu, the view method of the corresponding item is executed, which creates the view form.
This view form can have the New and Edit buttons, clicking on which the insert_record and edit_record methods will be executed. These methods create an item edit form.
Forms are based on HTML form templates that determine their layout. Form templates are defined in the Index.html file, located in the root folder of the project.
The application already has default templates for viewing and editing data, for specifying filters and report parameters.
For example, all edit forms of the Demo project use the following html template:
<div class="default-edit">
<div class="form-body">
<div class="edit-body"></div>
<div class="edit-detail"></div>
</div>
<div class="form-footer">
<button type="button" id="ok-btn" class="btn expanded-btn">
<i class="icon-ok"></i> OK<small class="muted"> [Ctrl+Enter]</small>
</button>
<button type="button" id="cancel-btn" class="btn expanded-btn">
<i class="icon-remove"></i> Cancel
</button>
</div>
</div>
You can define your own form templates to create your own custom forms. See Form templates
When some method creates a form the application finds corresponding html template.
If container
(a Jquery object) parameter is specified, the method empties it
and appends the html template to it, otherwise, it creates an empty modal form
and appends the template to the form.
After this it assigns item’s prefix_form
attribute to the template, triggers an on_prefix_form_created
events,
shows the form and triggers on_prefix_form_shown
events, where prefix is a
type of the form (view, edit, filter, param).
See Form events for details.
Below is an example of the on_edit_form_created
event handler of the task:
function on_edit_form_created(item) {
item.edit_form.find("#ok-btn").on('click.task', function() { item.apply_record() });
item.edit_form.find("#cancel-btn").on('click.task', function(e) { item.cancel_edit(e) });
if (!item.master && item.owner.on_edit_form_created) {
item.owner.on_edit_form_created(item);
}
if (item.on_edit_form_created) {
item.on_edit_form_created(item);
}
item.create_inputs(item.edit_form.find(".edit-body"));
item.create_detail_views(item.edit_form.find(".edit-detail"));
return true;
}
In this example, the the find
method of JQuery is used to to find
elements on the form.
First, we assign a JQuery click
event to OK and Cancel buttons,
so
cancel_edit
and
apply_record methods will be executed
when user clicks on the buttons. This methods cancel or apply changes made to
the record respectively and call the
close_edit_form
method to close the form.
Then, if item is not a detail and has an event handler on_edit_form_created
,
defined in the owner’s client module, this event handler is executed.
After that, if item has an event handler on_edit_form_created
,
defined in the item’s client module, this event handler is executed.
In these event handlers some additional actions could be executed. For example you can assign click events to buttons or some other elements contained in your edit form template, change edit_options, create tables using the create_table method and so on.
Then the create_inputs method is called to create inputs in the element with class “edit-body”
Finally, create_detail_views method is called to create details in the element with class “edit-detail”
Note
If some elements are missing in the form template, an exception will not be raised.
The close_prefix_form
, where prefix
is the type of the form, closes the
form of this type. But before form is closed the on_prefix_form_close_query
and on_prefix_form_closed
events are triggered. After form is closed it is
removed from the DOM.
Form templates of the project are located in the div with the templates
class
inside the body
tag in the
Index.html
file.
When
load
method is executed, it cuts out the div with templates
class from the
body
and stores it in the
templates
attribute as a JQuery object.
To add a form template for an item you should add a div with the name-suffix
class in the templates div, where name
is the
item_name
of the item and suffix
is the form type: view, edit, filter, param.
For example:
<div class="invoices-edit">
...
</div>
is an edit form template of the invoices item.
For a detail before its name there should be the name of its master, separated by a hyphen:
<div class="invoices-invoice_table-edit">
...
</div>
If an item doesn’t have a form template then the form template of its owner, if defined, will be used.
So the template
<div class="journals-edit">
...
</div>
will be used to create edit forms of items that Journals group owns and that do not have its own edit form template.
If, after searching this way, no template was found for an item, the template
with the default-suffix
class will
be used to create a form.
So the template
<div class="default-edit">
...
</div>
will be used to create edit forms for items that have no templates defined for them and their owners.
When a new project is created the index.html already contains such templates.
Below is an example of default edit form template from index.html file:
<div class="default-edit">
<div class="form-body">
<div class="edit-body"></div>
<div class="edit-detail"></div>
</div>
<div class="form-footer">
<button type="button" id="ok-btn" class="btn expanded-btn">
<i class="icon-ok"></i> OK<small class="muted"> [Ctrl+Enter]</small>
</button>
<button type="button" id="cancel-btn" class="btn expanded-btn">
<i class="icon-remove"></i> Cancel
</button>
</div>
</div>
There are more template examples in the Form examples section.
After the form is created and the HTML form template is added to the DOM, the application triggers the following form events during the life cycle of the form:
on_view_form_created
- the event is triggered when the form has been created but not shown yeton_view_form_shown
-the event is triggered when the the form has been shownon_view_form_close_query
- the event is triggered when an attempt is made to close the formon_view_form_closed
- the event is triggered when the form has been closedon_view_form_keydown
- the event is triggered when the keydown event occurs for the formon_view_form_keyup
- the event is triggered when the keyup event occurs for the formFor other form types - edit, filter and param, replace ‘view’ with the form type,
for example on_edit_form_created
for edit form.
We will first explain how to use the on_view_form_created
event.
When the user clicks on menu item the application executes the view method of corresponding task tree item, this method creates a form using its HTML form template and triggers first the on_view_form_created event of the task.
When you create a new project, the task client module already contains the code, including the on_view_form_created event handler. This event handler is executed each time the view form is created and defines the default behavior of view forms.
You can open the task client module to see this event handler. If you need to change the default behavior for all view forms of the project, you should do it here.
Below we describe the major steps it performs:
Initializes the view_form and table_options that are used by some methods when view form and table are created.
Assigns JQuery event handlers for default buttons to methods of the item, depending on the user rights. In the example below the delete button is. Initialized:
if (item.can_delete()) {
item.view_form.find("#delete-btn").on('click.task', function(e) {
e.preventDefault();
item.delete_record();
});
}
else {
item.view_form.find("#delete-btn").prop("disabled", true);
}
Executes the on_view_form_created event handler of the item group and. on_view_form_created of the item if they are defined:
if (!item.master && item.owner.on_view_form_created) {
item.owner.on_view_form_created(item);
}
if (item.on_view_form_created) {
item.on_view_form_created(item);
}
Creates a table to display the item data and tables for details if they have
been specified by calling create_view_tables
method
Executes open method, that gets the item dataset from the server.
Finally returns true to prevent calling of the on_view_form_created
of the
owner group and the item because the were already called see the
_process_event
method below.
After we initialized buttons and before creating tables we call the
on_view_form_created
event handler of the item itself.
For example, in the client module of the tracks item of the demo app the following on_view_form_created event handler is defined. In it we change the height attribute of the table_options , create the copy of the invoice_table set its attributes and call its create_table method that creates a table to display its data.
function on_view_form_created(item) {
item.table_options.height -= 200;
item.invoice_table = task.invoice_table.copy();
item.invoice_table.paginate = false;
item.invoice_table.create_table(item.view_form.find('.view-detail'), {
height: 200,
summary_fields: ['date', 'total'],
});
item.alert('Double-click the record in the bottom table to see track sales.');
}
The module also has the on_after_scroll event handler that will be executed when the user moves to the other track and will get the sales of this track.
This example explains the principle of form events usage.
The order of triggering of events depends on the type of event.
There are three type of
The order in which events are generated depends on the type of event.
When user tries to close the form the on_close_query event is first triggered (if defined) for the item.
If the event handler returns true the application closes the form, else if the event handler returns false the application leaves the form open, otherwise the on_close_query event is triggered (if defined) the same way for the item group and then for the task.
For example, by default there is the on_edit_form_close_query event handler in the task client module:
function on_edit_form_close_query(item) {
var result = true;
if (!item.virtual_table && item.is_changing()) {
if (item.is_modified()) {
item.yes_no_cancel(task.language.save_changes,
function() {
item.apply_record();
},
function() {
item.cancel_edit();
}
);
result = false;
}
else {
item.cancel_edit();
}
}
return result;
}
This code checks whether the record has been modified and then opens “Yes No Cancel” dialog.
If we want to close the form without this dialog we can defined the following event handler in the client module of the item:
function on_edit_form_close_query(item) {
item.cancel()
return true;
}
These events are triggered the same way as Close query events, starting from the item, but if the event handler returns true, the event handlers of the group and task are not executed.
For example, by default there is the on_edit_form_keyup event handler in the task client module:
function on_edit_form_keyup(item, event) {
if (event.keyCode === 13 && event.ctrlKey === true){
item.edit_form.find("#ok-btn").focus();
item.apply_record();
}
}
This code saves the changes of the record to the database table when user presses Ctrl+Enter.
Suppose we want to save the changes when user presses Enter. Then we write the following event handler in the item client module:
function on_edit_form_keyup(item, event) {
if (event.keyCode === 13){
item.edit_form.find("#ok-btn").focus();
item.apply_record();
return true;
}
}
In this case the event handler of the task won’t be called when the user press Enter.
For other events, the event handler of the task is called first, if it doesn’t return true, the event handler of the group is executed if it doesn’t return true the event handler of the item is called.
This mechanism is implemented the _process_event
method of the Item class in
the jam.js module.
_process_event: function(form_type, event_type, e) {
var event = 'on_' + form_type + '_form_' + event_type,
can_close;
if (event_type === 'close_query') {
if (this[event]) {
can_close = this[event].call(this, this);
}
if (!this.master && can_close === undefined && this.owner[event]) {
can_close = this.owner[event].call(this, this);
}
if (can_close === undefined && this.task[event]) {
can_close = this.task[event].call(this, this);
}
return can_close;
}
else if (event_type === 'keyup' || event_type === 'keydown') {
if (this[event]) {
if (this[event].call(this, this, e)) return;
}
if (!this.master && this.owner[event]) {
if (this.owner[event].call(this, this, e)) return;
}
if (this.task[event]) {
if (this.task[event].call(this, this, e)) return;
}
}
else {
if (this.task[event]) {
if (this.task[event].call(this, this)) return;
}
if (!this.master && this.owner[event]) {
if (this.owner[event].call(this, this)) return;
}
if (this[event]) {
if (this[event].call(this, this)) return;
}
}
}
For each type of form an item has an attribute that controls the modal form behavior:
This is an object that has the following attributes, specifing parameters of the modal form:
width
- the width of the modal form, the default value is 560 px,title
- the title of the modal form, the default value is the value of a
item_caption
attribute,close_button
- if true, the close button will be created in the upper-right
corner of the form, the default value is true,close_caption
- if true and close_button is true, will display ‘Close - [Esc]’
near the buttonclose_on_escape
- if true, pressing on the Escape key will trigger the
corresponding close_form method.close_focusout
- if true, the corresponding close_form method will be called
when a form loses focustemplate_class
- if specified, the div with this class will be searched in
the task
templates
attribute and used as a form html template when creating a formThe
edit_options
has a fields
attribute, that specify a list of field names that the
create_inputs
method will use, if fields
attribute of its options
parameter is not
specified, the default value is a list of field names set in the
Edit Form Dialog
in the Application builder.
The
view_options
has a fields
attribute, that specify a list of field names that the
create_table
method will use, if fields
attribute of its options
parameter is not
specified, the default value is a list of field names set in the
View Form Dialog
in the Application builder.
The width of the modal form, created in the following example, will be 700 px.
function on_edit_form_created(item) {
item.edit_options.width = 700;
}
To create a table to display an item’s dataset use create_table method:
item.create_table(item.view_form.find(".view-table"), table_options);
To create data controls to edit fields of the of the dataset use create_inputs method:
item.create_inputs(item.edit_form.find(".edit-body"), input_options);
These methods have two parameters - container and options. The first parameter is a JQuery container in which the controls will be placed. The second - options, satisfying the way the data will be displayed. For detailed information see their API reference.
The methods are usually used in the on_view_form_created and on_edit_form_created event handlers.
All visual controls (tables, inputs, checkboxes), created by this methods are data-aware. This means that they immediately reflect any changes of the item dataset.
Sometimes it is necessary to disable this interaction. To do so use the disable_controls and enable_controls methods respectively.
Jam.py framework uses a dataset concept that is very close to datasets of Embarcadero Delphi.
Note
There are other ways to read and modify the database data. You can use the connect method of the task to get a connection from the connection pool and use the connection to get access to the database using Python Database API.
All items with item_type
“item” or “table” as well as their details (see
Task tree)
can access data from associated tables from the project database and write
changes to it. They all are objects of
the Item class
Both of these classes have the same attributes, methods, and events associated with the data handling.
To get a dataset (a set of records) from the project dataset table, use the open method. This method, based on parameters, generates an SQL query to get a dataset.
After dataset is opened, the application can navigate it, change its records or insert new ones and write changes to the item’s database table.
For example, the following functions will set support_rep_id field values to the values of the id field on the client and server respectively:
function set_support_id(customers) {
customers.open();
while (!customers.eof()) {
customers.edit();
customers.support_rep_id.value = customers.id.value;
customers.post();
customers.next();
}
customers.apply();
}
def set_support_id(customers):
customers.open()
while not customers.eof():
customers.edit()
customers.support_rep_id.value = customers.id.value
customers.post()
customers.next()
customers.apply();
These functions get the customers item as a parameter. Then the open method is used to get a list of records from the customers table and each record is modified. In the end the changes are saved in the database table, using the apply method (see Modifying datasets ).
Note
There is a shorter way to navigate a dataset (see Navigating datasets ). For example, in python, the following loops are equivalent:
while not customers.eof():
print customers.firstname.value
customers.next()
for c in customers:
print c.firstname.value
Datasets and Datasets Part 2 demonstrate almost all methods of working with datasets on specific examples
When an application opens an item dataset, the dataset automatically enters browse state. Browsing enables you to view records in a dataset, but you cannot edit records or insert new records. You mainly use browse state to scroll from record to record in a dataset.
For more information about scrolling from record to record, see Navigating datasets.
From browse state all other dataset states can be set. For example, calling the insert or append methods changes its state from browse to insert.
Two methods can return a dataset to browse state. Cancel
ends the current
edit, insert, and returns a dataset to browse state. Post
writes changes
to the dataset, and if successful, also returns a dataset to browse state. If
this operations fail, the current state remains unchanged.
To check an item dataset state use item_state
attribute or is_new
is_edited
or is_changing
methods:
Client | Server | Description |
---|---|---|
item_state | item_state | Indicates the current operating state of the item dataset. |
is_new | is_new | Returns true if the item dataset is in insert state. |
is_edited | is_edited | Returns true if the item dataset is in edit state. |
is_changing | is_changing | Returns true if the item dataset is in insert or edit state. |
You can use the following item methods to insert, update, and delete data in dataset:
Client | Server | Description |
---|---|---|
edit | edit | Puts the item dataset into edit state. |
append | append | Appends a record to the end of the dataset, and puts the dataset in insert state. |
insert | insert | Inserts a record at the beginning of the dataset, and puts the dataset in insert state. |
post | post | Saves the new or altered record, and puts the dataset in browse state. |
cancel | cancel | Cancels the current operation and puts the dataset in browse state. |
delete | delete | Deletes the current record and puts the dataset in browse state. |
All changes made to the dataset are stored in memory, the item records changes
to change log. Thus, after all the changes have been made, they can be stored in
the associated database table by calling the apply
method. The apply
method generates and executes SQL query to save changes to the database.
Client | Server | Description |
---|---|---|
log_changes | log_changes | Indicates whether to log data changes. |
apply | apply | Sends all updated, inserted, and deleted records from the item dataset to the server for writing to the database. |
All items, working with database table data have a fields attribute - a list of field objects, which are used to represent fields in item’s table records.
Every field have the following attributes:
Client | Server | Description |
---|---|---|
owner | owner | The item that owns this field. |
field_name | field_name | The name of the field that will be used in programming code to get access to the field object. |
field_caption | field_caption | The name of the field that appears to users. |
field_type | field_type | Type of the field, one of the following values: text, integer, float, currency, date, datetime, boolean, blob. |
field_size | field_size | A size of the field with type text |
required | required | Specifies whether a nonblank value for a field is required. |
To get access to the item dataset data, the Field class have the following properties:
Client | Server | Description |
---|---|---|
value | value | Use this property to get or set the field’s value of the current record. When reading the value is converted to the type of the field. So for fields of type integer, float and currency, if value for this field in database table record is NULL, value of this property is 0. To get unconverted value use the raw_value property. |
text | text | Use this property to get or set the value of the field as text. |
lookup_value | lookup_value | Use this property to get or set lookup value, see Lookup fields. |
lookup_text | lookup_text | Use this property to get or set the lookup value of the field as text, see Lookup fields. |
display_text | display_text | Represents the field’s value as it is displayed in data-aware controls. When the field is a lookup field it’s value is the lookup_text value, otherwise it is the text value, with regard of project locale parameters. This behavior can be overridden by the on_field_get_text event handler of the item that owns the field. |
raw_value | raw_value | Use this property to get field value of the current record as it is stored in database. No conversion is used. |
In addition every field is an attribute of the item that owns it. So, to get
access to a field of an item use the following syntax: item.field_name
invoices.total.value
invoices.total
is the reference to the
Total field of the Invoices item and the
invoices.total.value
is the value of this field
Below are the values of the attributes of the fields of the invoices item in the Demo project
customer integer
value: 2
text: 2
lookup_value: Köhler
lookup_text: Köhler
display_text: Leonie Köhler
firstname integer
value: 2
text: 2
lookup_value: Leonie
lookup_text: Leonie
display_text: Leonie
billing_address integer
value: 2
text: 2
lookup_value: Theodor-Heuss-Straße 34
lookup_text: Theodor-Heuss-Straße 34
display_text: Theodor-Heuss-Straße 34
id integer
value: 1
text: 1
lookup_value: None
lookup_text:
display_text: 1
date date
value: 2014-01-01
text: 01/01/2014
lookup_value: None
lookup_text:
display_text: 01/01/2014
total currency
value: 2.08
text: $2.08
lookup_value: None
lookup_text:
display_text: $2.08
Items that have access to the database data can have common fields. They are defined in the group they belong to:
Here two fields are defined: id and deleted.
The id field is set as a primary key and will store a unique identifier for each record in the database table. This value is automatically generated by the framework when inserting a new record into the table.
The deleted field is set as a deletion flag. When the ‘Soft delete’ check-box is checked in the Item Editor Dialog, the delete method does not erase a record physically from the table, but uses this field to mark the record as deleted. The open method takes this into account when an SQL query is generated to get records from the database table.
For detail groups two more fields could be defined — master_id and master_rec_id. They are used to link detail records to the a record in master table, see Details.
A lookup field can display a user friendly value that is bound to another value in the another table or value list. For example, the lookup field can display a customer name that is bound to a respective customer ID number in another item’s table or list.
When entering a value in the lookup field the user chooses from a list of values. This can make data entry quicker and more accurate.
The two types of lookup fields that you can create are a lookup field, based on lookup item, and a value list.
In the framework you can add a field to an item to look up information in another item’s table. For example in the Demo application Albums catalog there is the Artist lookup field.
To set the value of the field the user must click on the button to the right of the field input and select a record from the ‘’Artists’’ catalog that will appear. Then the value of this field will be the id of the record. The other way to set value of the field is to use typeahead, if Typeahead flag is set in the Field Editor Dialog:
For such fields Lookup item and Lookup field must be specified in the Field Editor Dialog:
The SQL query that is generated on the server, when the open
method is called
and expanded
parameter is set to true (default), uses JOIN
clause to
get lookup values for such fields. Thus each such field has a pair of values:
the first value stores a reference to a record in the lookup item table (the value
of its primary key field), and the second value have the value of the lookup
field in this record.
To get access to this values use the following properties of lookup fields:
Client | Server | Description |
---|---|---|
value | value | A value, that is stored in the item table, that is a reference to a record in the lookup item table. |
lookup_value | lookup_value | A value of the lookup field in the lookup item table. |
Sometimes there is a need to have two or more values from the same record in the
lookup item table. For example, the “”Invoices” journal in Demo has several
lookup fields (“Customer”, “Billing Address”, “Billing City”, and so on)
that have information about a customer, all stored in one record in the
“Customers” item table, describing that customer. In order to avoid creating
unnecessary fields in the “Invoices” item table, storing the same reference
to a record, and creating JOIN
s for each such field, all lookup fields
except “Customers” have Master field value pointing to the “Customers”
field. These fields don’t have corresponding fields in the items’ underlying
database table. Their value property is always equal to the value property of
the master field and the SQL query that is generated on the server, when the
open method is called, uses one JOIN
clause for all this fields.
When user clicks on the button to the right of the field input or uses typeahead, the application creates a copy of the lookup item of the field, sets its lookup_field attribute to the field. and triggers on_field_select_value event. Write this event handler to specify fields that will be displayed, set up filters for the lookup item, before it will be opened and displayed for a user to select a value for the field.
The lookup field in the lookup item can also be a lookup field, for example:
To set up such a field use Lookup field 2 and Lookup field 3 attributes.
Sometimes a source of a lookup field can be defined as a value list. For example, a MediaType field in the Tracks catalog of the Demo project has a Lookup value list attribute set to the MediaTypes lookup list:
Use the Lookup List Dialog of the task to define such lookup lists.
There are three ways to define what records an item
dataset
will get from the database table
when the open
method is called:
where
parameter (option) of the open
method,set_where
method, before calling the open
method,When where
parameter is specified, it is always used even if the set_where
method was called or item has filters whose values have been set.
When where
parameter is omitted the parameter passed to the set_where
method are used.
For example on the client in the following code in the first call of the open
method the where
option will be used to filter records,
in the second call the parameters passed to set_where
and only the third
time the value of invoicedate1
filter will be used
function test(invoices) {
var date = new Date(new Date().setYear(new Date().getFullYear() - 1));
invoices.clear_filters();
invoices.filters.invoicedate1.value = date;
invoices.open({where: {invoicedate__ge: date}});
invoices.set_where({invoicedate__ge: date});
invoices.open();
invoices.open();
}
date = datetime.datetime.now() - datetime.timedelta(days=3*365)
The same code on the server looks the following way:
from datetime import datetime
def test(invoices):
date = datetime.now()
date = date.replace(year=date.year-1)
invoices.clear_filters()
invoices.filters.invoicedate1.value = date
invoices.open(where={'invoicedate__ge': date})
invoices.set_where(invoicedate__ge=date)
invoices.open()
invoices.open()
In the framework, the following symbols and corresponding constants are defined to filter records:
Filter type | Filter symbol | Constant | SQL Operator |
---|---|---|---|
EQ |
‘eq’ | FILTER_EQ |
= |
NE |
‘ne’ | FILTER_NE |
<> |
LT |
‘lt’ | FILTER_LT |
< |
LE |
‘le’ | FILTER_LE |
<= |
GT |
‘gt’ | FILTER_GT |
> |
GE |
‘ge’ | FILTER_GE |
>= |
IN |
‘in’ | FILTER_IN |
IN |
NOT IN |
‘not_in’ | FILTER_NOT_IN |
NOT IN |
RANGE |
‘range’ | FILTER_RANGE |
BETWEEN |
ISNULL |
‘isnull’ | FILTER_ISNULL |
IS NULL |
EXACT |
‘exact’ | FILTER_EXACT |
= |
CONTAINS |
‘contains’ | FILTER_CONTAINS |
uses LIKE with the “%” sign to find records where field value contains a search string |
STARTWITH |
‘startwith’ | FILTER_STARTWITH |
uses LIKE with the “%” sign to find records where field value starts with a search string |
ENDWITH |
‘endwith’ | FILTER_ENDWITH |
uses LIKE with the “%” sign to find records where field value ends with a search string |
CONTAINS ALL |
‘contains_all’ | FILTER_CONTAINS_ALL |
uses LIKE with the “%” sign to find records where field value contains all words of a search string |
The where
the parameter of the open
method is a dictionary, whose keys
are the names of the fields that are followed, after double underscore, by a
filter symbol. For EQ
filter the filtering symbol ‘__eq’ can be omitted.
For example {'id': 100}
is equivalent to {'id__eq': 100}
.
For each item that have access to a database table a list of filter objects can be created.
To create filters use an Filters Dialog of the Application builder.
Filters provide a convenient way for users to visually specify parameters of the request made by the application to the project database
Each filter has the following attributes:
owner
– an item that owners this filter,filter_name
— the name of the filter that can be used in programming codefilter_caption
- the name of the filter used in the visual representation
in the client application,filter_type
— type of the filter, see
Filtering records,visible
— if the value of this attribute is true
, a visual
representation of this filter will be created by the
create_filter_inputs
method, when a filters
option is not specified,All filters of the item are attributes of the filters
of its object.
By using filter_name
we can get access to the filter object:
invoices.filters.invoicedate1.value = new Date()
Another way to get access to the filter is to use filter_by_name method:
invoices.filter_by_name('invoicedate').value = new Date()
Details are used in the framework to work with tabular data, pertaining to a record in an item’s table.
For example, the Invoices journal in the Demo application has the InvoiceTable detail, which keeps a list of tracks in an customer’s invoice.
Details and detail items share the same underlying database table.
To create a detail, you must first create a detail item (select Details group of the project tree and click on New button) and then use the Details Dialog (select item in the project tree and click on Details button) to add a detail to an item.
For example the following code
def on_created(task):
task.invoice_table.open()
print task.invoice_table.record_count()
task.invoices.open(limit=1)
task.invoices.invoice_table.open()
print task.invoices.invoice_table.record_count()
will print:
2259
6
Details have two
common fields -
master_id
and master_rec_id
, that are used to store information about the
ID
of the master (each item have its own unique ID) and the value of the primary
field of the record of its master. This way each table can be linked to several
items. As well as each item can have several details. To get access to details of
an item use its details
attribute. To get access to the master of the detail
use its master
attribute.
Detail class, used to create details, is an ancestor of the Item class and inherits all its attributes, methods and events.
Note
The apply
method of the Detail class does nothing. To write changes made
to a detail use apply
method of its master.
To work with a detail its muster must be active
To make any changes to a detail its master must be in an edit or insert mode
In this example from the client module of the Invoices item of Demo project, the Invoice_table detail is reopened every time the cursor of its master moves to another record.
var ScrollTimeOut;
function on_after_scroll(item) {
clearTimeout(ScrollTimeOut);
ScrollTimeOut = setTimeout(
function() {
item.invoice_table.open(function() {});
},
100
);
}
And just as an example:
from datetime import datetime, timedelta
def on_created(task):
invoices = task.invoices.copy()
invoices.set_where(invoicedate__gt=datetime.now()-timedelta(days=1))
invoices.open()
for i in invoices:
i.invoice_table.open()
i.edit()
for t in i.invoice_table:
t.edit()
t.sales_id.value = '101010'
t.post()
i.post()
invoices.apply()
The same code on the client will be as follows:
function on_page_loaded(task) {
var date = new Date(),
invoices = task.invoices.copy();
invoices.set_where({invoicedate__gt: date.setDate(date.getDate() - 1)});
invoices.open();
invoices.each(function(i) {
i.invoice_table.open();
i.edit();
i.invoice_table.each(function(t) {
t.edit();
t.sales_id.value = '101010';
t.post();
});
i.post();
});
invoices.apply();
}
In most cases, the client sends a request to the server when following methods of an item are executed:
In these cases the client sends to the server the ID of the item’s task, the ID of the item, the type of the request and its parameters.
The server on receiving the request, based on passed IDs, finds the task (it can be Project task or Application builder task) and the item on the server, executes the corresponding method with passed parameters and returns the result of the execution to the client. The server method can trigger events that can modify its default behavior.
Every item of the task tree have the environ and session attributes that store context of the current request.
The most common server events are:
apply
method of the item is called on the client or the serveropen
method of the item is called on the client or the serverNote
Note that the task tree on the server is immutable, you can not change the attributes of the items in the task tree.
You must use the copy method to create a copy of an item. This copy is an exact copy of an item at the time of creating of the task tree. It is not added to the task tree and will be destroyed by Python garbage collector when no longer needed.
When the apply
method of the item is called
on the client or the
server, the server application, by default,
generates SQL query, based on changes made to the dataset and executes it.
This behavior can be changed by writing an on_apply event handler in the item server module.
Sometimes it becomes necessary to execute some code, when changes are saved, for
all items. In this case the on_apply
event handler of the task (declared
in the task server module) can be used.
The following code describes how these events are handled:
#...
result = None
if self.task.on_apply:
result = self.task.on_apply(self, delta, params, connection)
if result is None and self.on_apply:
result = self.on_apply(self, delta, params, connection)
if result is None:
result = self.apply_delta(delta, params, connection)
#...
return result
It checks if the task has an on_apply
event handler. If the on_apply
event handler is declared in the task server module, it is executed.
If the on_apply
event handler of the task is not declared or the result
of the event handler returns None
, the method checks whether the item has an
on_apply
event handler. If it is declared in the item server module, it is executed.
If the result returned by the item event handler is None
, the
apply_delta
method of the item is called that generates SQL query,
execute it and returns the result
When the open
method of the item is called
on the client or the
server, the server application
executes the following code:
result = None
if self.task.on_open:
result = self.task.on_open(self, params)
if result is None and self.on_open:
result = self.on_open(self, params)
if result is None:
result = self.execute_open(params)
It checks if the task has an on_open
event handler. If the on_open
event handler is declared in the task server module, it is executed.
If the on_open
event handler of the task is not declared or the result
of the event handler returns None
, the method checks whether the item has an
on_open
event handler. If it is declared in the item server module, it is executed.
If the result returned by the item event handler is None
, the
execute_open
method of the item is called that generates SQL query,
execute it and returns the result
To create a report, you must first prepare a report template in LibreOffice Calc.
The template files are located in the report folder of the project directory.
The following figure shows a template of the Invoice report.
Reports in Jam.py are band-oriented.
Each report template is divided into bands. To set bands use the leftmost column of a template spreadsheet.
In the Invoice report template there are three bands: title, detail and summary.
In addition, templates can have programmable cells.
For example, in the template of Invoice report the I7 cell contains the text %(date)s.
Programmable cell begins with %, then follows the name of the cell in the parenthesis which is followed by character s.
To add a new report to Jam.py project, choose the Reports node in the project tree, the click the New button and fill in the caption, name and the template file name of the report.
If a visible checkbox is set, the default code adds the report to the Reports menu of the project.
You can specify the parameters of the report. For example, the Customer purchases report of the Demo project have three parameters.
To add or change a report parameter click Report params button in the left panel of the Application builder. A form will appear displaying the list of existing parameters. Then click New or Edit button of the form to add or change the parameter.
In the dialog box fill in:
You can create a lookup parameter, For example, the Customer purchases report has a Customer parameter that can be selected from Customers catalog:
In this case you should specify
Form for setting the parameters of Customer purchases report is as follows:
To print a report on the client use the print method.
As a result of calling this function, the client calls create_param_form method to create a form for editing the report parameters, based on the html template defined in the index.html file (see Forms).
This method, after creating the form, triggers the following events:
The default code has the on_param_form_created event handler, defined for the task. In this event, the click on the Print button is connected to the report’s process_report method.
function on_param_form_created(item) {
item.create_param_inputs(item.param_form.find(".edit-body"));
item.param_form.find("#ok-btn").on('click.task', function() {
item.process_report()
});
item.param_form.find("#cancel-btn").on('click.task', function() {
item.close_param_form()
});
}
In its turn the process_report method triggers
In this event handlers developer can define some common (report group event handler) or specific (report event handler) attributes of the report.
For example, in the default code, there is the on_before_print_report event handler of the report group, in which report’s extension attribute is defined:
function on_before_print_report(report) {
var select;
report.extension = 'pdf';
if (report.param_form) {
select = report.param_form.find('select');
if (select && select.val()) {
report.extension = select.val();
}
}
}
In the following event handler, defined in the client module of the invoice report of the Demo application, the value of the report id parameter is set:
function on_before_print_report(report) {
report.id.value = report.task.invoices.id.value;
}
After that the process_report method sends asynchronous request to the server to generate the report (see Server-side programming).
The server returns to the method an url to a file with generated report.
The method then checks if the on_open_report event handler of the report group is defined. If this events handler if defined calls it, otherwise checks the on_open_report of the report. If it is defined then calls it.
If none of this events are defined, it (depending on the report extension attribute) opens the report in the browser or saves it to disc.
When a server gets a request from a client to generate report, it first of all creates a copy of the report and then this copy calls the generate method.
This method triggers the on_before_generate event. In this event handler developer should write a code that generates the content of the report.
For example for the invoice report of the Demo application this event is as follows:
def on_generate(report):
invoices = report.task.invoices.copy()
invoices.set_where(id=report.id.value)
invoices.open()
customer = invoices.firstname.display_text + ' ' + invoices.customer.display_text
address = invoices.billing_address.display_text
city = invoices.billing_city.display_text + ' ' + invoices.billing_state.display_text + ' ' + \
invoices.billing_country.display_text
date = invoices.invoicedate.display_text
shipped = invoices.billing_address.display_text + ' ' + invoices.billing_city.display_text + ' ' + \
invoices.billing_state.display_text + ' ' + invoices.billing_country.display_text
taxrate = invoices.taxrate.display_text
report.print_band('title', locals())
tracks = invoices.invoice_table
tracks.open()
for t in tracks:
quantity = t.quantity.display_text
track = t.track.display_text
unitprice = t.unitprice.display_text
sum = t.amount.display_text
report.print_band('detail', locals())
subtotal = invoices.subtotal.display_text
tax = invoices.tax.display_text
total = invoices.total.display_text
report.print_band('summary', locals())
First, we use the copy method to create a copy of the invoices journal.
invoices = report.task.invoices.copy()
We create the copiy because multiple users can simultaneously generate the same report in parallel threads.
Then we call the set_where method of the copy:
invoices.set_where(id=report.id.value)
where report.id.value is report id parameter, the value of which we set in the on_before_print_report event handler on the client and which is equal to the current id field value of the invoice journal.
Then, using the open method, we obtain the records on the server. After that the print_band method is used to print title band:
report.print_band('title', locals())
But before that we assign values to four local variables: customer, address, city and date that correspond to programmable cells in the title band in the report template.
Then the same way we generate detail and summary bands.
When the report is generated and the value of report extension attribute, set on the client, is not equals ‘pdf’ the server converts the ods file using LibreOffice.
Once the report is generated it is stored in a report folder of the static directory and the server sends the client the report file url.
When a new project is created, its task tree has the following groups: Catalogs, Journals, Details and Reports.
Catalogs and Journals belong to the Item Group type and have the same functional purpose. See Groups.
We created them to distinguish between two types of data items:
To upgrade an existing project to a new package you must update the package.
You can do it using pip.
If you’re using Linux, Mac OS X or some other flavor of Unix, enter the command:
sudo pip install --upgrade jam.py
If you’re using Windows, start a command shell with administrator privileges and run the command
pip install --upgrade jam.py
Foreign keys that you can create in the Application Builder prevent deletion of a record in the lookup table if a reference to it is stored in the lookup field.
For example, when a foreign key is created on the “Customer” field for “Invoices” item, user won’t be able to delete a customer in “Customers” catalog if a reference to it is stored in “Invoices”.
The soft delete attribute of the lookup item must be set to false (see Item Editor Dialog ) for the lookup field to appear in the Foreign Keys Dialog
You can add javascript libraries to use them for programming on the client side.
It is better to place them in the js folders of the static directory of the project. And refer to them using the src attribute in the <script> tag of the Index.html file.
For example, Demo project uses Chart.js library to create a dashboard:
<script src="/static/js/Chart.min.js"></script>
On the server side you can import python libraries to your modules.
For exapmple the mail item server module import smtplib library to send emails:
import smtplib
When a report is generated the server application first creates an ods file.
If extension attribute of the report is set to ‘pdf’ or any other format except ‘ods’, the application first creates an ods file and then uses LibreOffice in “headless” mode to convert the ods file to that format.
If LibreOffice is currently running on the server this conversion may not happen. You must close LibreOffice on the server for the conversion to take place.
Here is a useful code that you can use in your applications:
Adapted from Django Docs
The below document is adopted from Django Docs.
This document will guide you through installing Python 3.x and Jam.py on Windows. It also provides instructions for setting up a virtual environment, which makes it easier to work on Python projects. This is meant as a beginner’s guide for users working on Jam.py projects and does not reflect how Jam.py should be installed when developing patches for Jam.py itself.
The steps in this guide have been tested with Windows 10. In other versions, the steps would be similar. You will need to be familiar with using the Windows command prompt.
Jam.py is a Python web framework, thus requiring Python to be installed on your machine. At the time of writing, Python 3.8 is the latest version.
To install Python on your machine go to https://www.python.org/downloads/. The website should offer you a download button for the latest Python version. Download the executable installer and run it. Check the boxes next to “Install launcher for all users (recommended)” then click “Install Now”.
After installation, open the command prompt and check that the Python version matches the version you installed by executing:
...\> py --version
pip
¶pip is a package manager for Python and is included by default with the
Python installer. It helps to install and uninstall Python packages
(such as Jam.py!). For the rest of the installation, we’ll use pip
to
install Python packages from the command line.
It is best practice to provide a dedicated environment for each Jam.py project you create. There are many options to manage environments and packages within the Python ecosystem, some of which are recommended in the Python documentation.
To create a virtual environment for your project, open a new command prompt, navigate to the folder where you want to create your project and then enter the following:
...\> py -m venv project-name
This will create a folder called ‘project-name’ if it does not already exist and set up the virtual environment. To activate the environment, run:
...\> project-name\Scripts\activate.bat
The virtual environment will be activated and you’ll see “(project-name)” next to the command prompt to designate that. Each time you start a new command prompt, you’ll need to activate the environment again.
Jam.py can be installed easily using pip
within your virtual environment.
In the command prompt, ensure your virtual environment is active, and execute the following command:
...\> py -m pip install jam.py
This will download and install the latest Jam.py release.
After the installation has completed, you can verify your Jam.py installation
by executing pip list
in the command prompt.
If you are connecting to the internet behind a proxy, there might be problems
in running the command py -m pip install Jam.py
. Set the environment
variables for proxy configuration in the command prompt as follows:
...\> set http_proxy=http://username:password@proxyserver:proxyport
...\> set https_proxy=https://username:password@proxyserver:proxyport
If your Administrator prohibited setting up a virtual environment, it is still possible to install Jam.py as follows:
...\> python -m pip install jam.py
This will download and install the latest Jam.py release.
After the installation has completed, you can verify your Jam.py installation
by executing pip list
in the command prompt.
However, running jam-project.py
will fail since it is not in the path. Check
the installation folder:
...\> python -m site --user-site
The output might be similar to below:
C:\Users\youruser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages
Replace site-packages
at the end of above line with Scripts
:
...\> dir C:\Users\youruser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\Scripts
The output might be similar to below:
...\> Directory of C:\Users\yourser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\Scripts
13/04/2023 02:59 PM <DIR> .
13/04/2023 02:59 PM <DIR> ..
13/04/2023 02:59 PM 1,087 jam-project.py
1 File(s) 1,087 bytes
2 Dir(s) 177,027,321,856 bytes free
Create the new folder somewhere and run jam-project
from from it:
...\> python C:\Users\youruser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\Scripts\jam-project.py
Run the new project:
...\> python server.py
Migrating development to production is very simple in Jam.py due to the ability to export and import its metadata.
To understand the concept of metadata and the process of exporting and importing metadata, please read the topic Export/import metadata. The process of importing metadata depends on the type of project database.
Note
For projects with SQLite database you can simply copy the development project folder to the production environment.
Note
For SQLite database, Jam.py doesn’t support importing of metadata into an existing project (project with tables in the database). You can only import metadata into a new project.
Stop the server and copy the metadata zip file to migration folder in the project directory. If the folder doesn’t exist, create it.
Start the server. The web application, while initializing itself, will import the metadata file. You can see the information on how the file was imported in the log file in the logs folder of the project directory. If the import is successful, the zip file will be deleted.
Click the Import button in the Application Builder.
Note
By default the web application in the process that imports the metadata waits for 5 minutes or until all previous request to the application in this process will be processed before it starts to change the database. For projects that run on multiple processes you can set the Import delay parameter in the Parameters to delay the change the database or use Importing metadata with server shutdown.
You can mirgate your data to another database.
For example, you developed your project with SQLite database amd want to move to Postgress.
To do this, follow these steps:
Create an empty Postgress database
Create a new project with this database
Export the metadata of the SQLite project to a zip file in the Application Builder by clicking the Export button.
Import the metadata to the new project. The web application with create database structures in the Postgress database.
copy data from SQlite to Postgress database using the copy_database method of the task:
create in the sever module of the task the following function:
from jam.db.db_modules import SQLITE
def copy_db(task):
task.copy_database(SQLITE, '/home/work/demo/demo.sqlite')
then you can execute it one of the following ways:
call this function in the on_created event handler:
from jam.db.db_modules import SQLITE
def copy_db(task):
task.copy_database(SQLITE, '/home/work/demo/demo.sqlite')
def on_created(task):
copy_db(task)
create a button in some form and use the task server method to execute it
function on_view_form_created(item) {
item.add_view_button('Copy DB').click(function() {
task.server('copy_db')
});
}
or run from from debbuging console of the browser:
task.server('copy_db')
Remove the code that was used.
Note
You can not migrate to SQLite database of the current database has foreign keys
Use pip to install Jam.py. To do this, open the bash console and run the following command (for Python 3.7):
pip3.7 install --user jam.py
Create a zip archive of your project folder, upload the archive in the Files tab and unzip it.
We assume that you are registered as username and your project is now located in the /home/username/project_folder directory.
Open the Web Tab. Add a new web app. In the Code section specify
In the WSGI configuration file:/var/www/username_pythonanywhere_com_wsgi.py file add the following code
import os
import sys
path = '/home/username/project_folder'
if path not in sys.path:
sys.path.append(path)
from jam.wsgi import create_application
application = create_application(path)
Reload the server.
This is adapted from https://devops.profitbricks.com/tutorials/install-and-configure-mod_wsgi-on-ubuntu-1604-1/
I hope someone finds it useful.
Create an AWS account and login
Go to EC2, create an instance (in this case an Ubuntu 16.04 t2.micro)
Download the private key when prompted
Convert pem to ppk using Puttygen (see: https://stackoverflow.com/questions/3190667/convert-pem-to-ppk-file-format)
Get EC2 instance public DNS from AWS dashboard
SSH into EC2 instance using Putty (pointed to the Public DNS and your ppk)
Username is ubuntu
Refresh package library:
sudo apt-get update
Install pip:
sudo apt-get install python3-pip
Install jam.py:
sudo pip3 install jam.py
Install Apache:
sudo apt-get install apache2 apache2-utils libexpat1 ssl-cert
Install mod-wsgi:
sudo apt-get install libapache2-mod-wsgi-py3
Restart Apache:
sudo /etc/init.d/apache2 restart
Move here:
cd /var/www/html/
Create directory:
sudo mkdir [appname]
Move here:
cd [appname]
Create app:
sudo jam-project.py
Check it’s there:
ls
Create the config:
sudo nano /etc/apache2/conf-available/wsgi.conf
Paste the following
WSGIScriptAlias / /var/www/html/[appname]/wsgi.py
WSGIPythonPath /var/www/html/[appname]
<Directory /var/www/html/[appname]>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
Alias /static/ /var/www/html/[appname]/static/
<Directory /var/www/html/[appname]/static>
Require all granted
</Directory>
Exit and save
Give file permissions to apache:
sudo chmod 777 /var/www/html/[appname]
Give ownership to apache:
sudo chown -R www-data:www-data /var/www
Enable wsgi:
sudo a2enconf wsgi
Restart apache:
sudo /etc/init.d/apache2 restart
Create security group on AWS to allow you to connect HTTP on port 80
Assign instance to security group
Test
If it’s not working, check the error logs to see what’s going on:
nano /var/log/apache2/error.log
This was initialy published by Simon Cox on https://groups.google.com/forum/#!msg/jam-py/Zv5JfkLRFy4/22tolZ-hAQAJ
So basically deploying straight into the ie an cloud server with open 22, 80 and 443 port. Prerequisite is a signed certificate for the DNS server name (YOUR_SERVER DNS entry from below). One can use a self signed, etc, not covering those. Also, Python installed and sudo access (or root for Linux). I have no idea at all about the MS Servers, sorry.
The App is in read only mode. You can access admin.html page, but can’t change anything. Took me some fiddling with Google Cloud server, this is a micro Ubuntu instance, plain apache2 install with apt-get.
Install wsgi module for Apache :
apt-get install libapache2-mod-wsgi
Enable ssl, wsgi module for apache:
a2enmod ssl wsgi
Create a custom file for jam-py app, ie /etc/apache2/sites-available/test.conf, for example (still wip):
<IfModule mod_ssl.c>
<VirtualHost YOUR_IP:443>
ServerName YOUR_SERVER
ServerAlias
ServerAdmin YOUR_EMAIL
ErrorLog ${APACHE_LOG_DIR}/test-error-sec.log
CustomLog ${APACHE_LOG_DIR}/test-access-sec.log combined
#below is for cx_Oracle
SetEnv LD_LIBRARY_PATH /u01/app/oracle/product/11.2.0/xe/lib
SetEnv ORACLE_SID XE
SetEnv ORACLE_HOME /u01/app/oracle/product/11.2.0/xe
#finish cx_Oracle
DocumentRoot /var/www/html/simpleassets
SSLEngine on
SSLCertificateFile "/etc/ssl/private/your.crt"
SSLCertificateKeyFile "/etc/ssl/private/your.key"
SSLCertificateChainFile "/etc/ssl/private/your_chain.crt"
SSLCACertificateFile "/etc/ssl/private/your_CA.crt"
WSGIDaemonProcess web user=www-data group=www-data processes=1 threads=5
WSGIScriptAlias / /var/www/html/simpleassets/wsgi.py
<Directory /var/www/html/simpleassets>
Options +ExecCGI
SetHandler wsgi-script
AddHandler wsgi-script .py
Order deny,allow
Allow from all
Require all granted
<Files wsgi.py>
Order deny,allow
Allow from all
# comment the following for ubuntu <13
Require all granted
</Files>
</Directory>
<Directory /var/www/html/simpleassets/static>
# comment the following for ubuntu < 13
Require all granted
</Directory>
</VirtualHost>
</IfModule>
The above file is using signed certificate your.crt with your.key, and CA, chain file obtained from CA. Please review resources on the net about certificates and the dns. You’ll need to obtain and copy those files in /etc/ssl/private folder. Change YOUR_xyz with your preference.
The /var/www/html is the default Ubuntu folder for serving web pages.
Install jam-py as usual.
I created the /var/www/html/simpleassets folder where unzipped jam-py SimpleAssets project. Follow procedure explained there how to deploy these:
Basically, Export your project, save the zip file and copy it to your web hosting server desired folder. Copy admin.sqlite and your database as well (providing you’re using sqlite3 database). If using some other database ie mysql, you’ll need to export/import the database.
Enable test.conf (the above file name with no extension):
a2ensite test; systemctl restart apache2
That is it. At the moment, I’ve left port 80 as is, and jam-py is running only on https port. To debug problems, I would start with SeLinux or apparmor. With Ubuntu this might help:
sudo /etc/init.d/apparmor stop
Now, here is the question of how to run TWO jam-py instances on one https server?
One possible answer to this problem is the DNS. You might decide to set your DNS to ie second_instance.YOUR_SERVER name (the above live example would be jam2.research…).
So the above test.conf file would be almost the same except YOUR_SERVER is now called second_instance.YOUR_SERVER
The /etc/apache2/sites-available/test3.conf file:
<IfModule mod_ssl.c>
<VirtualHost YOUR_IP:443>
ServerName second_instance.YOUR_SERVER
ServerAlias
ServerAdmin YOUR_EMAIL
ErrorLog ${APACHE_LOG_DIR}/test3-error-sec.log
CustomLog ${APACHE_LOG_DIR}/test3-access-sec.log combined
#below is for cx_Oracle
SetEnv LD_LIBRARY_PATH /u01/app/oracle/product/11.2.0/xe/lib
SetEnv ORACLE_SID XE
SetEnv ORACLE_HOME /u01/app/oracle/product/11.2.0/xe
#finish cx_Oracle
DocumentRoot /var/www/html/simpleassets3
SSLEngine on
SSLCertificateFile "/etc/ssl/private/your.crt"
SSLCertificateKeyFile "/etc/ssl/private/your.key"
SSLCertificateChainFile "/etc/ssl/private/your_chain.crt"
SSLCACertificateFile "/etc/ssl/private/your_CA.crt"
WSGIDaemonProcess assets3 user=www-data group=www-data processes=1 threads=5
WSGIScriptAlias / /var/www/html/simpleassets3/wsgi.py
<Directory /var/www/html/simpleassets3>
Options +ExecCGI
SetHandler wsgi-script
AddHandler wsgi-script .py
Order deny,allow
Allow from all
Require all granted
<Files wsgi.py>
Order deny,allow
Allow from all
# comment the following for ubuntu <13
Require all granted
</Files>
</Directory>
<Directory /var/www/html/simpleassets3/static>
# comment the following for ubuntu < 13
Require all granted
</Directory>
</VirtualHost>
</IfModule>
The jam-py application second_instance lives now in ie /var/www/html/simpleassets3, and WSGIDaemonProcess is adjusted to new daemon, called assets3. Everything else is almost the same.
This is possible because the SSL certificate is a * (star, or wildcard) certificate, enabling you to run multiple services on one DNS domain.
This was initialy published by Dražen Babić on https://github.com/jam-py/jam-py/issues/35
Green Unicorn (gunicorn) is an HTTP/WSGI server designed to serve fast clients or sleepy applications. That is to say; behind a buffering front-end server such as nginx or lighttpd.
By default, gunicorn will listen on 127.0.0.1. Navigate to jam App folder, or use (ie in scripts, cron job, etc)
python /usr/bin/gunicorn --chdir /path/to/jam/App wsgi
or from /path/to/jam/App:
gunicorn wsgi
[2018-04-13 15:01:44 +0000] [8650] [INFO] Starting gunicorn 19.4.5
[2018-04-13 15:01:44 +0000] [8650] [INFO] Listening at: http://127.0.0.1:8000 (8650)
[2018-04-13 15:01:44 +0000] [8650] [INFO] Using worker: sync
[2018-04-13 15:01:44 +0000] [8654] [INFO] Booting worker with pid: 8654
.
.
To start jam.py on all interfaces and port 8081:
gunicorn -b 0.0.0.0:8081 wsgi
[2018-04-13 15:03:34 +0000] [8680] [INFO] Starting gunicorn 19.4.5
[2018-04-13 15:03:34 +0000] [8680] [INFO] Listening at: http://0.0.0.0:8081 (8680)
[2018-04-13 15:03:34 +0000] [8680] [INFO] Using worker: sync
[2018-04-13 15:03:34 +0000] [8684] [INFO] Booting worker with pid: 8684
.
.
Spin up 5 workers if u like with –workers=5
Nginx:
comment out default location in /etc/nginx/sites-enabled/default (Linux Mint):
#location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
# try_files $uri $uri/ =404;
# }
and add:
# Proxy connections to the application servers
# app_servers
location / {
proxy_pass http://app_servers;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
add in /etc/nginx/nginx.conf 127.0.0.1:8081 if this is your Gunicorn server address and port:
# Configuration containing list of application servers
upstream app_servers {
server 127.0.0.1:8081;
}
This also enables to have different App servers on different ports
Client Request ----> Nginx (Reverse-Proxy)
|
/|\
| | `-> App. Server I. 127.0.0.1:8081
| `--> App. Server II. 127.0.0.1:8082
`----> App. Server III. 127.0.0.1:8083
Restart nginx and viola!
Congratulations! We can now test Nginx with Jam.py.
Now, certs:
in /etc/nginx/sites-enabled/jam we can have something like this to pass everything from http to https to 8001 port (or any other as per above):
server {
listen 80;
server_name YOUR_SERVER;
access_log off;
location /static/ {
alias /path/to/jam/App/static/;
}
location / {
proxy_pass http://127.0.0.1:8001;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';
}
return 301 https://$server_name$request_uri;
}
server {
listen 443;
server_name YOUR_SERVER_FQDN;
access_log off;
location /static/ {
alias /path/to/jam/App/static/;
}
location = /favicon.ico {
alias /path/to/jam/App/favicon.ico;
}
ssl on;
ssl_certificate /etc/nginx/ssl/YOUR_SERVER.crt;
ssl_certificate_key /etc/nginx/ssl/YOUR_SERVER.key;
add_header Strict-Transport-Security "max-age=31536000";
location / {
client_max_body_size 10M;
proxy_pass http://127.0.0.1:8001;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';
}
That’s it!
Congratulations! We can now test Nginx with Jam.py on https port!
This was initialy published by Dražen Babić on https://github.com/jam-py/jam-py/issues/67
Each function defined in the server or client module of an item becomes an attribute of the item.
Thus, using the task tree, you can access any function declared in the client or server module in any project module.
For example, if we have a function some_func
declared in the Customers client
module, we can execute it in any module of the project.
Note that the task is a global variable on the client.
task.customers.some_func()
On the server, the task is not global, but an item that triggered / called it is
passed to each event handler and function called by the
server
method. Therefore, if the some_func
function is declared in the Customers
server module, it can be executed in a function or event handler as follows:
def on_apply(item, delta, params):
item.task.customers.some_func()
Note that event handlers are just functions and can also be called from other modules.
Write the on_field_validate event handler to validate field value.
For example, The event will triggered when the post method is called, that saves the record in memory or when the user leaves the input used to edit the unitprice field value.
function on_field_validate(field) {
if (field.field_name === 'unitprice' && field.value <= 0) {
return 'Unit price must be greater that 0';
}
}
As an example, below is the code that doesn’t use the on_field_validate method and checks the value of the unitprice field and prevents the user from leaving the input when the value is less than or equal to zero:
function on_edit_form_shown(item) {
item.each_field( function(field) {
var input = item.edit_form.find('input.' + field.field_name);
input.blur( function(e) {
var err;
if ($(e.relatedTarget).attr('id') !== "cancel-btn") {
err = check_field_value(field);
if (err) {
item.alert_error(err);
input.focus();
}
}
});
});
}
function check_field_value(field) {
if (field.field_name === 'album' && !field.value) {
return 'Album must be specified';
}
if (field.field_name === 'unitprice' && field.value <= 0) {
return 'Unit price must be greater that 0';
}
}
In the on_edit_form_shown event handler, we iterate through all the fields using the each_field method and find the input data for each field, if it exists.
In the on_edit_form_shown event handler we iterate through all the fields using the each_field method and find the input for each field, if it exists. Each input has a class with the name of the field (field_name).
Then we assign a jQuery blur event to it, in which we call the check_field_value
function, and, if it returns text string, we warn the user and focus the input.
Before calling the function, we check whether the “Cancel” button was pressed.
We declared the on_edit_form_shown event handler in the item’s module, so it will work in this module only.
We can declare the following event handler in the task client module so we can
write check_field_value
function in any module we need to enable this field
validation. The
on_edit_form_shown of the task
is called first for every item when edit form is shown. See
Form events.
function on_edit_form_shown(item) {
if (item.check_field_value) {
item.each_field( function(field) {
var input = item.edit_form.find('input.' + field.field_name);
input.blur( function(e) {
var err;
if ($(e.relatedTarget).attr('id') !== "cancel-btn") {
err = item.check_field_value(field);
if (err) {
item.alert_error(err);
input.focus();
}
}
});
});
}
}
In this event handler we check if the item has the check_field_value
attribute.
Each function declared in a module becomes an attribute of the item.
The simplest way to add a button to an edit / view from is to use add_edit_button / add_view_button correspondingly. You can call this functions in the on_edit_form_created / on_view_form_created event handlers.
For example the Customers item uses this code in its client module to add buttons to a view form:
function on_view_form_created(item) {
item.table_options.multiselect = false;
if (!item.lookup_field) {
var print_btn = item.add_view_button('Print', {image: 'icon-print'}),
email_btn = item.add_view_button('Send email', {image: 'icon-pencil'});
email_btn.click(function() { send_email() });
print_btn.click(function() { print(item) });
item.table_options.multiselect = true;
}
}
In this code the item’s
lookup_field
attribute is checked and if it is defined (the view form is not created to select a
value for a lookup field) the two buttons are created and for them JQuery click
events are assigned to send_email
and print
functions declared in that
module.
You can use server method to send a request to the server to execute a function defined in the server module of an item.
En the example below we create the btn
button that is a JQuery object.
Then we use its click method to attach a function that calls the
server
method of the item to run the calculate
function defined in the server module
of the item.
The code in the client module:
function on_view_form_created(item) {
var btn = item.add_view_button('Calculate', {type: 'primary'});
btn.click(function() {
item.server('calulate', [1, 2, 3], function(result, error) {
if (error) {
item.alert_error(error);
}
else {
console.log(result);
}
})
});
}
The code in the server module:
def calculate(item, a, b, c):
return a + b + c
You can access any DOM element on forms using jQuery.
In the following example, in the on_edit_form_created
event handler defined
the item client module we find the OK button, hide it, and change the
text of the Cancel button to “Close” in the edit form:
function on_edit_form_created(item) {
item.edit_form.find("#ok-btn").hide();
item.edit_form.find("#cancel-btn").text('Close');
}
When an application creates input controls, it adds a class with a name that is the field_name attribute of the corresponding field to each input.
Thus, using the jQuery selectors, we can find the input of the customer field as follows (we select the input with the “customer” class in the edit form):
item.edit_form.find("input.customer")
Having found the element of the form you can use JQuery methods to change it.
As the field inputs are created by
create_inputs
after the
on_edit_form_created
event have been triggered (see the on_edit_form_created
event handler in the
task client module) you must write
on_edit_form_shown
event handler to change inputs.
For example this code
function on_edit_form_shown(item) {
item.edit_form.find('input.name').css('color', 'red');
item.edit_form.find('input.name').css('font-size', '24px');
item.edit_form.find('input.tracks_sold').width(20);
item.edit_form.find('input.genre').parent().width('40%');
item.edit_form.find('input.composer').prop('type', 'password');
}
will change form inputs this way:
Please, note that if you need to change the width of input with prepend or append buttons (inputs of date, datetime and lookup fields) set the width of the input parent:
item.edit_form.find('input.album').parent().width('50%');
Another way to change the style of DOM elements is to use CSS. When the task node is selected in the Application Builder, the “project css” button is located on the right pane. Click on it to open the project.css file, which is located in the project folder. You can use it to input CSS that defines the style of the DOM elements of the project.
Each item form created in the project has css classes that enable developer to identify the form.
Each form has a class identifying it’s type: ‘view-form’, ‘edit-form’, ‘filter-form’ or ‘param-form’.
For example, the following code will remove the images in the buttons at the bottom of the form:
.view-form .form-footer .btn i {
display: none;
}
More edit form examples:
.edit-form #ok-btn {
font-weight: bold;
background-color: lightblue;
}
.edit-form.invoices input.total {
color: red;
}
Also each form has a class with a name that is the item_name attribute of the item.
The following code will remove images in the buttons only in the Invoices view form:
.view-form.invoices .form-footer .btn i {
display: none;
}
You can change the way tables are displayed. The tables that are created by the create_table method have a css class “dbtable” and a class with a name that is the item_name attribute of the item. each column of the table alse have a class with a name that is the field_name attribute of the corresponding field.
The example, the following code will display cells of the Invoices table Customer column bold:
.dbtable.invoices td.customer {
font-weight: bold;
}
One more way to change the way the field colum is displayed is to write the on_field_get_html event handler.
For example:
function on_field_get_html(field) {
if (field.field_name === 'total') {
if (field.value > 10) {
return '<strong>' + field.display_text + '</strong>';
}
}
}
You must first call the open method of the item to initiate its dataset. For example, if you want to add a new record to invoices in the Demo application, you can do so as follows:
var invoices = task.invoices.copy();
invoices.open({ open_empty: true });
invoices.append_record();
In this code, we create a copy of the item using the copy method so that this operation does not affect the Invoices view form if it is open in a tab.
You can also change the record, but before you do this, you must get it from the server. Below is the code that modifies the record with id 411. We check that the record exists using the rec_count property, otherwise we display a warning.
var invoices = task.invoices.copy();
invoices.open({ where: {id: 411} });
if (invoices.rec_count) {
invoices.edit_record();
}
else {
invoices.alert_error('Invoices: record not found.');
}
In the example above the open method is not executed syncroniously.
The code below does it asyncroniously:
var invoices = task.invoices.copy();
invoices.open({ where: {id: 411} }, function() {
if (invoices.rec_count) {
invoices.edit_record();
}
else {
invoices.alert_error('Invoices: record not found.');
}
});
Invoices has the Modeless attribute set in the Edit form dialog, so the the edit form with be opened in a tab. You can change it by setting modeless attribute of edit_options to make the edit form modal:
var invoices = task.invoices.copy();
invoices.edit_options.modeless = false;
Let’s assume that we have an item with a boolean field “posted”, and if the value of the field is true, we must prohibit changing or deleting the record.
We can do this by writing the on_after_scroll event handler and using permissions property:
function on_after_scroll(item) {
if (item.rec_count) {
item.permissions.can_edit = !item.posted.value;
item.permissions.can_delete = !item.posted.value;
if (item.view_form) {
item.view_form.find("#delete-btn").prop("disabled", item.posted.value);
}
}
}
In this event handler we check the value of the “posted” field and set the permissions property attributes to true.
We can also write the on_apply event handler in the server module of the item:
def on_apply(item, delta, params, connection):
for d in delta:
if d.posted.old_value:
raise Exception('Document posted. No change allowed')
We’ll explain how to link two items on example of the tracks and invoicetable items from the demo application. We’ll link the record of tracks with the corresponding list of sold tracks from invoicetable that contains all sold tracks from invoices.
The default behavior if view_form is defined in the on_view_form_created event handler declared in the task client module.
We will change it in the on_view_form_created event handler in the tracks client module
function on_view_form_created(item) {
item.table_options.height -= 200;
item.invoice_table = task.invoice_table.copy();
item.invoice_table.paginate = false;
item.invoice_table.create_table(item.view_form.find('.view-detail'), {
height: 200,
summary_fields: ['date', 'total'],
});
}
Then we reduce height of the table that displays tracks data by 200 pixels
item.table_options.height -= 200;
create a copy of invoice_table, set its paginate attribute to false and call the create_table method to create a table that will display the sold tracks
item.invoice_table = task.invoice_table.copy();
item.invoice_table.paginate = false;
item.invoice_table.create_table(item.view_form.find('.view-detail'), {
height: 200,
summary_fields: ['date', 'total'],
});
For this table we set the height to 200 pixels and define to summary fields.
This table will always be empty if we won’t define the following on_after_scroll event handler:
function on_after_scroll(item) {
if (item.view_form.length) {
if (item.rec_count) {
item.invoice_table.set_where({track: item.id.value});
item.invoice_table.set_order_by(['-invoice_date']);
item.invoice_table.open(true);
}
else {
item.invoice_table.close();
}
}
}
The on_after_scroll event is triggered whenever the current record is changed. So when the track is changed we call open method, pre-setting the filter and order
item.invoice_table.set_where({track: item.id.value});
item.invoice_table.set_order_by(['-invoice_date']);
item.invoice_table.open(true);
This method sends a request to the server, that generates sql query, executes it and returns a dataset that contains sold records of this track ordered in descending order of invoice_date field.
If the tracks dataset is empty we clear the sold records dataset by calling the close method.
Because controls in Jam.py are data-aware every change of sold records dataset will be displayed in the table that we created in the on_view_form_created event handler.
Now every time the track has changed the application send request to the server to renew the sold tracks. This is not effective and sometimes can lead to delays. To avoid this we use the JavaScript setTimeout function:
var scroll_timeout;
function on_after_scroll(item) {
if (!item.lookup_field && item.view_form.length) {
clearTimeout(scroll_timeout);
scroll_timeout = setTimeout(
function() {
if (item.rec_count) {
item.invoice_table.set_where({track: item.id.value});
item.invoice_table.set_order_by(['-invoice_date']);
item.invoice_table.open(true);
}
else {
item.invoice_table.close();
}
},
100
);
}
}
This function guarantees that the data will be updated no more than once every 100 milliseconds.
Since the invoicetable is a detail it has the master_rec_id field that stores a reference to invoice that has this record, we can show the user an invoice that contains the current sold record. To do so we pass to the create_table method the function that will be executed when user double click the record:
item.invoice_table.create_table(item.view_form.find('.view-detail'), {
height: 200,
summary_fields: ['date', 'total'],
on_dblclick: function() {
show_invoice(item.invoice_table);
}
});
and define the function as follows:
function show_invoice(invoice_table) {
var invoices = task.invoices.copy();
invoices.set_where({id: invoice_table.master_rec_id.value});
invoices.open(function(i) {
i.edit_options.modeless = false;
i.can_modify = false;
i.invoice_table.on_after_open = function(t) {
t.locate('id', invoice_table.id.value);
};
i.edit_record();
});
}
In this function we create a copy of the invoices journal and find the invoice. When the open method is executed we will show the invoice by calling its edit_record method. But before calling it we set its attributes so that it will be modal and the user won’t be able to modify it.
Besides we dynamically assign on_after_open event handler to the invoice_table detail of the invoice we get. In this event handler we will find the current record in the sold records by calling the locate method.
Finally we will check the lookup_field attribute of tracks. This attribute is true if the item was created to select a value for the lookup field when a user clicks on the button to the right of lookup field input. We will make so that the sold tracks are not shown when the user selects the value for the lookup field.
In addition, we add an alert informing the user about the possibility of seeing the invoice.
Finally the code of the on_view_form_created will be as follows:
function on_view_form_created(item) {
if (!item.lookup_field) {
item.table_options.height -= 200;
item.invoice_table = task.invoice_table.copy();
item.invoice_table.paginate = false;
item.invoice_table.create_table(item.view_form.find('.view-detail'), {
height: 200,
summary_fields: ['date', 'total'],
on_dblclick: function() {
show_invoice(item.invoice_table);
}
});
item.alert('Double-click the record in the bottom table ' +
'to see the invoice in which the track was sold.');
}
}
var scroll_timeout;
function on_after_scroll(item) {
if (!item.lookup_field && item.view_form.length) {
clearTimeout(scroll_timeout);
scroll_timeout = setTimeout(
function() {
if (item.rec_count) {
item.invoice_table.set_where({track: item.id.value});
item.invoice_table.set_order_by(['-invoice_date']);
item.invoice_table.open(true);
}
else {
item.invoice_table.close();
}
},
100
);
}
}
function show_invoice(invoice_table) {
var invoices = task.invoices.copy();
invoices.set_where({id: invoice_table.master_rec_id.value});
invoices.open(function(i) {
i.edit_options.modeless = false;
i.can_modify = false;
i.invoice_table.on_after_open = function(t) {
t.locate('id', invoice_table.id.value);
};
i.edit_record();
});
}
In this example, we will show how to change the “Media Type” field of the “Tracks” catalog to the same value for the selected records.
First we set the multiselect attribute of the table_options to true to display the check box in the leftmost column of the “Tracks” table for the user to select the records and create the Set media type button in the on_view_form_created event handler in the client module of “Tracks”.
function on_view_form_created(item) {
item.table_options.multiselect = true;
item.add_view_button('Set media type').click(function() {
set_media_type(item);
});
}
When this button is pressed, the set_media_type
function defined in the
module is executed.
In this function we create a copy of the “Tracks” item. We pass to the copy method the handlers option equal to false. It means that all the settings to the item made in the Form Dialogs in the Application Builder and all the functions and events defined in the client module of the item will be unavailable to the copy.
Then we analyze the selections attribute that is the array of the values of primary key field of the records, selected by the user.
After it we initialize the dataset of the copy by calling the open method with open_empty option. We also set the fields options so that the dataset will have only one field media_type. We set the required attribute of that field to true.
And finally, before calling the append_record method, we dynamically assign the on_edit_form_created event handler to change the on click event of the OK button, that was defined in the client module of the task.
In the new on click event handler we, first, call the post method to check that the media type value is set, if exception is raised we call edit method to allow the user to set it.
function set_media_type(item) {
var copy = item.copy({handlers: false}),
selections = item.selections;
if (selections.length > 1000) {
item.alert('Too many records selected.');
}
else if (selections.length || item.rec_count) {
if (selections.length === 0) {
selections = [item.id.value];
}
copy.open({fields: ['media_type'], open_empty: true});
copy.edit_options.title = 'Set media type to ' + selections.length +
' record(s)';
copy.edit_options.history_button = false;
copy.media_type.required = true;
copy.on_edit_form_created = function(c) {
c.edit_form.find('#ok-btn').off('click.task').on('click', function() {
try {
c.post();
item.server('set_media_type', [c.media_type.value, selections],
function(res, error) {
if (error) {
item.alert_error(error);
}
if (res) {
item.selections = [];
item.refresh_page(true);
c.cancel_edit();
item.alert(selections.length + '
record(s) have been modified.');
}
}
);
}
finally {
c.edit();
}
});
};
copy.append_record();
}
}
When the user clicks the OK button, the item’s
server
method executes the set_media_type
function on the server, which changes the
field value of the selected records.
After changing the records on the server we, on the client, unselect the records, refresh the data of the page, cancel editing by calling the cancel_edit method and inform the user of the results.
def set_media_type(item, media_type, selections):
copy = item.copy()
copy.set_where(id__in=selections)
copy.open(fields=['id', 'media_type'])
for c in copy:
c.edit()
c.media_type.value = media_type
c.post()
c.apply()
return True
You can do it by adding a button that will save the record without closing the edit form.
Below is examples for synchronous and asynchronous cases.
function on_edit_form_created(item) {
var save_btn = item.add_edit_button('Save and continue');
save_btn.click(function() {
if (item.is_changing()) {
item.post();
try {
item.apply();
}
catch (e) {
item.alert_error(error);
}
item.edit();
}
});
}
function on_edit_form_created(item) {
var save_btn = item.add_edit_button('Save and continue');
save_btn.click(function() {
if (item.is_changing()) {
item.disable_edit_form();
item.post();
item.apply(function(error){
if (error) {
item.alert_error(error);
}
item.edit();
item.enable_edit_form();
});
}
});
}
Below is two examples.
In the first example each apply method gets its own connection from connection pool and commits it after saveing changes to the database.
In the second example the connection is received from connection pool and passed to each apply method so changes are commited at the end.
import datetime
def change_invoice_date(item, invoice_id):
now = datetime.datetime.now()
invoices = item.task.invoices.copy(handlers=False)
invoices.set_where(id=invoice_id)
invoices.open()
invoices.edit()
invoices.invoice_date.value = now
invoices.post()
invoices.apply()
customer_id = invoices.customer.value
customers = item.task.customers.copy(handlers=False)
customers.set_where(id=customer_id)
customers.open()
customers.edit()
customers.last_modified.value = now
customers.post()
customers.apply()
import datetime
def change_invoice_date(item, invoice_id):
now = datetime.datetime.now()
con = item.task.connect()
try:
invoices = item.task.invoices.copy(handlers=False)
invoices.set_where(id=invoice_id)
invoices.open()
invoices.edit()
invoices.invoice_date.value = now
invoices.post()
invoices.apply(con)
customer_id = invoices.customer.value
customers = item.task.customers.copy(handlers=False)
customers.set_where(id=customer_id)
customers.open()
customers.edit()
customers.last_modified.value = now
customers.post()
customers.apply(con)
con.commit()
finally:
con.close()
One of the ways to do it is to write the on_apply event handler.
In the example below, the delta parameter is a dataset that contains the changes that will be stored in the users table.
We go through the records of changes and if the record was not deleted or the login field didn’t change we look for a record in the table with the same login and if it exists raise the exception. If the user is editing the record on the client using an edit form he won’t be able to save it and will see the corresponding alert message.
def on_apply(item, delta, params, connection):
for d in delta:
if not (d.rec_deleted() or d.rec_modified() and d.login.value == d.login.old_value):
users = d.task.users.copy(handlers=False)
users.set_where(login=d.login.value)
users.open(fields=['login'])
if users.rec_count:
raise Exception('There is a user with this login - %s' % d.login.value)
You can implement a multi-tenancy using Jam.py.
For example, if some item have a user_id field, the following code in the server module of the item will do the job:
def on_open(item, params):
if item.session:
user_id = item.session['user_info']['user_id']
if user_id:
params['__filters'].append(['user_id', item.task.consts.FILTER_EQ, user_id])
def on_apply(item, delta, params, connection):
if item.session:
user_id = item.session['user_info']['user_id']
if user_id:
for d in delta:
if d.rec_inserted():
d.edit()
d.user_id.value = user_id
d.post()
elif d.rec_modified():
if d.user_id.old_value != user_id:
raise Exception('You are not allowed to change record.')
elif d.rec_deleted():
if d.user_id.old_value != user_id:
raise Exception('You are not allowed to delete record.')
It uses a session attribute of the item to get a unique user id and on_open and on_apply event handlers.
The on_open event handler ensures that the sql select statement that applications generates will return only records where the user_id field will be the same as the ID of the user that sends the request.
And the on_apply event handler sets the user_id to the ID of the user that appended or modified the records.
You can use a more general approach and add the following code to the server module of the task. Then a multi-tenancy will be applied to every item that have a user_id field:
def on_open(item, params):
if item.field_by_name('user_id'):
if item.session:
user_id = item.session['user_info']['user_id']
if user_id:
params['__filters'].append(['user_id', item.task.consts.FILTER_EQ, user_id])
def on_apply(item, delta, params, connection):
if item.field_by_name('user_id'):
if item.session:
user_id = item.session['user_info']['user_id']
if user_id:
for d in delta:
if d.rec_inserted():
d.edit()
d.user_id.value = user_id
d.post()
elif d.rec_modified():
if d.user_id.old_value != user_id:
raise Exception('You are not allowed to change record.')
elif d.rec_deleted():
if d.user_id.old_value != user_id:
raise Exception('You are not allowed to delete record.')
Please read this: Intergation with existing database
You can use data from other database tables.
First you must specify table name and fields information. You can do it the following way:
Then in the server module of the new items you must add code to read and write the data to the database
Below is the code for MySQL database (auto incremented primary field):
import MySQLdb
from jam.db import mysql
def on_open(item, params):
connection = item.task.create_connection_ex(mysql, database='demo', \
user='root', password='111', host='localhost', encoding='UTF8')
try:
sql = item.get_select_query(params, mysql)
rows = item.task.select(sql, connection, mysql)
finally:
connection.close()
return rows, ''
def on_apply(item, delta, params):
connection = item.task.create_connection_ex(mysql, database='demo', \
user='root', password='111', host='localhost', encoding='UTF8')
try:
sql = delta.apply_sql(params, mysql)
result = item.task.execute(sql, None, connection, mysql)
finally:
connection.close()
return result
If database use generators to get primary field values you must specify them for new records (Firebird):
import fdb
from jam.db import firebird
def on_open(item, params):
connection = item.task.create_connection_ex(firebird, database='demo.fdb', \
user='SYSDBA', password='masterkey', encoding='UTF8')
try:
sql = item.get_select_query(params, firebird)
rows = item.task.select(sql, connection, firebird)
finally:
connection.close()
return rows, ''
def get_id(table_name, connection):
cursor = connection.cursor()
cursor.execute('SELECT NEXT VALUE FOR "%s" FROM RDB$DATABASE' % (table_name + '_SEQ'))
r = cursor.fetchall()
return r[0][0]
def on_apply(item, delta, params):
connection = item.task.create_connection_ex(firebird, database='demo.fdb', \
user='SYSDBA', password='masterkey', encoding='UTF8')
for d in delta:
if not d.id.value:
d.edit()
d.id.value = get_id(item.table_name, connection)
for detail in d.details:
for r in detail:
if not r.id.value:
r.edit()
r.id.value = get_id(r.table_name, connection)
r.post()
d.post()
try:
sql = delta.apply_sql(params, firebird)
result = item.task.execute(sql, None, connection, firebird)
finally:
connection.close()
return result
You can use the task on_open
and on_apply
events. Below is the code
from task client module:
import MySQLdb
from jam.db import mysql
def on_open(item, params):
if item.item_name in ['table1', 'table2']: # or
#if item.table_name in ['table1', 'table2']:
connection = item.task.create_connection_ex(mysql, database='demo', \
user='root', password='111', host='localhost', encoding='UTF8')
try:
sql = item.get_select_query(params, mysql)
rows = item.task.select(sql, connection, mysql)
finally:
connection.close()
return rows, ''
def on_apply(item, delta, params):
if item.item_name in ['table1', 'table2']:
connection = item.task.create_connection_ex(mysql, database='demo', \
user='root', password='111', host='localhost', encoding='UTF8')
try:
sql = delta.apply_sql(params, mysql)
result = item.task.execute(sql, None, connection, mysql)
finally:
connection.close()
return result
Note
Do not set History attribute to True for this tables. If you do so you’ll get
the exception. History table must be one for all databases that you use in
the project.
You can try to create the history table in the other database and write the
on_open
and on_apply
event handlers for it.
You can access the data of your application for reading and writing by sending a post request that has ‘ext’ added to url. For example:
http://example.com/ext/edit
When an web app on the server receives such request it generates the on_ext_request event
You can use this code in the task server module to run a background thread in the web application once a 3 minutes (can be changed by setting interval) to perform some calculations:
import threading
import time
import traceback
def background(task):
interval = 3 * 60
time.sleep(interval)
while True:
if not time:
return
with task.lock('background'):
try:
print('background')
# some code to execute in background for example:
# tracks = task.tracks.copy()
# tracks.open()
# for t in tracks:
# t.edit()
# t.sold.value = #some value
# t.post()
# tracks.apply()
except Exception as e:
traceback.print_exc()
time.sleep(interval)
def on_created(task):
bg = threading.Thread(target=background, args=(task,))
bg.daemon = True
bg.start()
Note
When multiple web applications are running in parallel processes, the background function will be executed in each process. To prevent simultaneous execution of this function, we use the lock method of the task.
Yes, you can have details inside details.
Suppose we have three objects - “Polls”, “Questions” and “Answers.” “Answers” is a detail of “Questions”. We will make “Questions” a detail of “Polls”.
One way to do this is to add an integer field “poll” to the “Questions” and the following code to the “Poll” client module:
function on_edit_form_created(item) {
var q = task.questions.copy();
item.edit_form.find('.form-footer').hide();
q.view_options.form_header = false;
q.on_view_form_created = function(quest) {
quest.paginate = false;
};
q.on_before_append = function(quest) {
if (!item.id.value) {
quest.alert_error('Poll is not specified.');
quest.abort();
}
};
q.on_before_post = function(quest) {
quest.poll.value = item.id.value;
};
q.set_where({poll: item.id.value});
q.view(item.edit_form.find('.edit-detail'));
}
function on_field_changed(field, lookup_item) {
var item = field.owner;
item.apply();
item.edit();
}
function on_before_delete(item) {
var q = task.questions.copy();
q.set_where({poll: item.id.value});
q.open();
while (!q.eof()) {
q.delete();
}
q.apply();
}
First, in the client module of the item we create two buttons that execute the corresponding functions when you click on them:
function on_view_form_created(item) {
var csv_import_btn = item.add_view_button('Import csv file'),
csv_export_btn = item.add_view_button('Export csv file');
csv_import_btn.click(function() { csv_import(item) });
csv_export_btn.click(function() { csv_export(item) });
}
function csv_export(item) {
item.server('export_csv', function(file_name, error) {
if (error) {
item.alert_error(error);
}
else {
var url = [location.protocol, '//', location.host, location.pathname].join('');
url += 'static/files/' + file_name;
window.open(encodeURI(url));
}
});
}
function csv_import(item) {
task.upload('static/files', {accept: '.csv', callback: function(file_name) {
item.server('import_csv', [file_name], function(error) {
if (error) {
item.warning(error);
}
item.refresh_page(true);
});
}});
}
These functions execute the following functions defined in the server module. In this module we use the Python csv module. We do not export system fields - primary key field and deletion flag field.
Below is the code for Python 3:
import os
import csv
def export_csv(item):
copy = item.copy()
copy.open()
file_name = item.item_name + '.csv'
path = os.path.join(item.task.work_dir, 'static', 'files', file_name)
with open(path, 'w', encoding='utf-8') as csvfile:
fieldnames = []
for field in copy.fields:
if not field.system_field():
fieldnames.append(field.field_name)
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for c in copy:
dic = {}
for field in copy.fields:
if not field.system_field():
dic[field.field_name] = field.text
writer.writerow(dic)
return file_name
def import_csv(item, file_name):
copy = item.copy()
path = os.path.join(item.task.work_dir, 'static', 'files', file_name)
with open(path, 'r', encoding='utf-8') as csvfile:
copy.open(open_empty=True)
reader = csv.DictReader(csvfile)
for row in reader:
print(row)
copy.append()
for field in copy.fields:
if not field.system_field():
field.text = row[field.field_name]
copy.post()
copy.apply()
For Python 2, this code looks like this:
import os
import csv
def export_csv2(item):
copy = item.copy()
copy.open()
file_name = item.item_name + '.csv'
path = os.path.join(item.task.work_dir, 'static', 'files', file_name)
with open(path, 'wb') as csvfile:
fieldnames = []
for field in copy.fields:
if not field.system_field():
fieldnames.append(field.field_name.encode('utf8'))
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for c in copy:
dic = {}
for field in copy.fields:
if not field.system_field():
dic[field.field_name.encode('utf8')] = field.text.encode('utf8')
writer.writerow(dic)
return file_name
def import_csv2(item, file_name):
copy = item.copy()
path = os.path.join(item.task.work_dir, 'static', 'files', file_name)
with open(path, 'rb') as csvfile:
item.task.execute('delete from %s' % item.table_name)
copy.open(open_empty=True)
reader = csv.DictReader(csvfile)
for row in reader:
print(row)
copy.append()
for field in copy.fields:
if not field.system_field():
field.text = row[field.field_name.encode('utf8')].decode('utf8')
copy.post()
copy.apply()
In the Jam.py repository there is the “Authentication” project export file. This project demonstrates the first three topics of this section.
http://jam-py.com/repository/auth.zip
You can download it, create a new project and import this file.
By default, all user information is stored in a table in the admin.sqlite database. This table has a fixed structure that cannot be changed.
In this section, we describe how to authenticate a user using data from the custom users table.
First, we create an item group Authentication select it and add an item Users that has the following fields:
We won’t store in the table the user password and use this field in the interface. We will store the password salted hash in the password_hash field.
We also created the lookup list “Roles” that we used in the “Roles” field definition.
We added to it the same roles (ids and names) as in the table Roles We ‘ll have to sycronize this roles in the future.
In the Roles it is necessary to allow view the Users item only people that will be responsible for it
We removed password_hash field from field lists in the View Form Dialog and Edit Form Dialog
In the User server module we define the following on_apply event handler:
def on_apply(item, delta, params, connection):
for d in delta:
if not (d.rec_deleted() or d.rec_modified() and d.login.value == d.login.old_value):
users = d.task.users.copy(handlers=False)
users.set_where(login=d.login.value)
users.open(fields=['login'])
if users.rec_count:
raise Exception('There is a user with this login - %s' % d.login.value)
if d.password.value:
d.edit();
d.password_hash.value = delta.task.generate_password_hash(d.password.value)
d.password.value = None
d.post();
In this event handler we check if there is a users with the same login and raise the exception if such user exists, otherwise we generate hash using the generate_password_hash method of the task and set the password value to None.
In the client module we defined the following on_field_get_text event handler. It displays ‘******’ string insted of the password.
function on_field_get_text(field) {
var item = field.owner;
if (field.field_name === 'password') {
if (item.id.value || field.value) {
return '**********';
}
}
}
Finally, we define the on_login event handler in the task server module:
def on_login(task, form_data, info):
users = task.users.copy(handlers=False)
users.set_where(login=form_data['login'])
users.open()
if users.rec_count == 1:
if task.check_password_hash(users.password_hash.value, form_data['password']):
return {
'user_id': users.id.value,
'user_name': users.name.value,
'role_id': users.role.value,
'role_name': users.role.display_text
}
Now we must add an admin to Users that has rights to work with users. After that we can set Safe mode in the project Parameters
In this topic we’ll assume that you have created a Users item from the previous topic.
Now we create a register.html file.
It contains a registration form:
<form id="login-form" target="dummy" class="form-horizontal" style="margin: 0;">
<div class="control-group">
<label class="control-label" for="name">Name</label>
<div class="controls">
<input type="text" id="name" placeholder="Login">
</div>
</div>
<div class="control-group">
<label class="control-label" for="login">Login</label>
<div class="controls">
<input type="text" id="login" placeholder="Login">
</div>
</div>
<div class="control-group">
<label class="control-label" for="password1">Password</label>
<div class="controls">
<input type="password" id="password1"
placeholder="Password" autocomplete="on">
</div>
</div>
<div class="control-group">
<label class="control-label" for="password2">Repeat password</label>
<div class="controls">
<input type="password" id="password2"
placeholder="Repeat password" autocomplete="on">
</div>
</div>
<div class="alert alert-success" style="margin: 0; display: none">
You have been successfully registered.
</div>
<div class="alert alert-error" style="margin: 0; display: none">
</div>
<div class="form-footer">
<input type="button" class="btn expanded-btn pull-right"
id="register-btn" value="OK" tabindex="3">
</div>
</form>
and a javascript code:
$(document).ready(function(){
function register(name, login, password) {
$.ajax({
url: "ext/register",
type: "POST",
contentType: "application/json;charset=utf-8",
data: JSON.stringify([name, login, password]),
success: function(response, textStatus, jQxhr) {
if (response.result.data) {
show_alert(response.result.data);
}
else {
$("div.alert-success").show();
setTimeout(
function() {
window.location.href = "index.html";
},
1000
);
}
},
error: function(jqXhr, textStatus, errorThrown) {
console.log(errorThrown);
}
});
}
function show_alert(message) {
$("div.alert-error")
.text(message)
.show();
}
$('input').focus(function() {
$("div.alert").hide();
});
$("#register-btn").click(function() {
var name = $("#name").val(),
login = $("#login").val(),
password1 = $("#password1").val(),
password2 = $("#password2").val();
if (!name) {
show_alert('Name is not specified');
}
else if (!login) {
show_alert('Login is not specified');
}
else if (!password1) {
show_alert('Password is not specified');
}
else if (password1 !== password2) {
show_alert('Passwords do not match');
}
else {
register(name, login, password1)
}
})
})
When the user clicks on the OK button, the javascript will send to the server the ajax post request with url “ext/register” and parameters “name, login, password”.
When server receives the request starting with ‘ext/’ it triggers the on_ext_request event.
The task server module has the following on_ext_request
event handler:
def on_ext_request(task, request, params):
reqs = request.split('/')
if reqs[2] == 'register':
name, login, password = params
users = task.users.copy(handlers=False)
users.set_where(login=login)
users.open()
if users.rec_count:
return 'Existing login, please use different login'
users.append()
users.name.value = name
users.login.value = login
users.password_hash.value = task.generate_password_hash(password)
users.role.value = 2
users.post()
users.apply()
It checks if there is ‘register’ in url and then looks if there is no user with the login and then register the user.
First we create a “Change password” item. While creating it we set the “Virtual table” and “Visible” attributes to false in the Item Editor Dialog. And we add to it two fields: “Old password”, “New password”
We’ll use this item for displaying “Change password” dialog.
To open this dialog we add a “Change password” menu item with id “pass” in the index.html:
<div class="container">
<div id="taskmenu" class="navbar">
<div class="navbar-inner">
<ul id="menu" class="nav">
</ul>
<ul id="menu-right" class="nav pull-right">
<li id="pass"><a href="#">Change password</a></li>
</ul>
</div>
</div>
</div>
and in the task client module on_page_loaded event handler add the following code:
if (task.change_password.can_view()) {
$("#menu-right #pass a").click(function(e) {
e.preventDefault();
task.change_password.open({open_empty: true});
task.change_password.append_record();
});
}
else {
$("#menu-right #pass a").hide();
}
It will check if the user has the right to view item and then opens an empty dataset and creates an edit form, otherwise it hides this menu item.
In the “Change password” client module we add the following code:
function on_edit_form_created(item) {
item.edit_form.find("#ok-btn")
.off('click.task')
.on('click', function() {
change_password(item);
});
item.edit_form.find("#cancel-btn")
.off('click.task')
.on('click', function() {
item.close_edit_form();
});
}
function change_password(item) {
item.post();
item.server('change_password', [item.old_password.value, item.new_password.value], function(res) {
if (res) {
item.warning('Password has been changed. <br> The application will be reloaded.',
function() {
task.logout();
location.reload();
});
}
else {
item.alert_error("Can't change the password.");
item.edit();
}
});
}
function on_field_changed(field, lookup_item) {
var item = field.owner;
if (field.field_name === 'old_password') {
item.server('check_old_password', [field.value], function(error) {
if (error) {
item.alert_error(error);
}
});
}
}
function on_edit_form_close_query(item) {
return true;
}
In it we reassign OK and Cancel button click events. By default they
are defined in the task client module to save record changes to the database
and cancel editing. In the on_edit_form_close_query
even handler we return
true so the on_edit_form_close_query
declared in the task client module,
that shows “Yes No Cancel” disalog won’t be executed.
The on_field_changed
event handler will check if old password is correct.
It and the change_password
function send requests to the server to execute
functions defined in the item server module:
def change_password(item, old_password, new_password):
user_id = item.session['user_info']['user_id']
users = item.task.users.copy(handlers=False)
users.set_where(id=user_id)
users.open()
same_password = item.task.check_password_hash(users.password_hash.value, old_password)
if users.rec_count== 1 and same_password:
users.edit()
users.password_hash.value = item.task.generate_password_hash(new_password)
users.post()
users.apply()
return True
else:
return False
def check_old_password(item, old_password):
user_id = item.session['user_info']['user_id']
users = item.task.users.copy(handlers=False)
users.set_where(id=user_id)
users.open()
same_password = item.task.check_password_hash(users.password_hash.value, old_password)
if users.rec_count == 1 and same_password:
return
else:
return 'Invalid password'
They use session to get id of the current user.
After changing the password the client reloads.
Application builder - is a Jam.py web application intended for application development and database administration.
To run the Application builder go to a Web browser and type in the browser address bar
127.0.0.1:8080/builder.html
Note
Please note that server.py must be running
On the left side of the Application builder page there is a panel that contains the project tree. When you select any node of the project tree, as a rule, its content will be opened in the central part of the page, and the bottom and right side of the page may have buttons that allow you to modify the content.
To see the changes made in Application builder go to the Project page and reload it.
To prevent Cross Site Scripting (XSS) attacks, Jam.py sanitizese field values displayed in the table columns.
For example, if field contains the following text:
"<span style='color: red'>USA</span>"
when unsanitized it will be displayed in the table column as follows:
When the field text sanitized, it is transformed to the following:
"<span style='color: red'>USA</span>"
as you can see symbols ‘<’ and ‘>’ are replaced with ‘<’ and ‘>’ and the table column will be displayed this way:
There are two ways to prevent sanitizing.
First is to set Do not sanitize attribute in the Interface tab in the Field Editor Dialog
Second is to write the on_field_get_html event handler. If the this event handler returns a value it is not sanitized.
An accept string can be a combination of the following values, separated by comma.
Value | Description |
---|---|
file_extension | Specify the file extension(s) (e.g: .gif, .jpg, .png, .doc) |
audio/* | All sound files |
video/* | All video files |
image/* | All image files |
For example:
.pdf,.xls
image/*,.pdf,.xls
audio/*
audio/*,video/*
After the Application builder is first run or when the Project node is selected in the project tree, the Application builder page will look as follows:
Click on the links below to see the purpose of the buttons in the right panel of the page.
After clicking on the Parameters button the Parameters Dialog will appear. It has two tabs General and Interface.
On the General tab, you can specify general parameters of the project:
Note
When Connection pool size or Persistent connection parameters are changed, the server application must be restarted for changes to take effect.
On the Interface tab, you can specify interface parameters of the project:
In this dialog project database parameters are displayed. When they have been changed and OK button is clicked, the Application builder will check connection to the database and if it failed to connect an error will be displayed.
Note
When any Database parameter is changed, except DB manual update, the server application must be restarted for changes to take effect.
If DB manual update checkbox is unchecked (default), then when changes to an item, that have an associated database table, are saved, this database table is automatically modified. For example, if we add a new field to some item in the Item Editor Dialog , the new field will be added to the associated database table. If this checkbox is checked, no modifications to the database tables are made.
Note
Please be very careful when using this option.
Press this button to print all modules of the project.
All the code, parameters and data structure information of the project is stored in the admin.sqlite SQLite database located in the project folder. This information we call the metadata.
The project metadata can be exported to a zip file in the Application Builder by clicking the Export button.
This file contains the following information:
The metadata file can be imported to another project.
The web application while importing the metadata performs the following operations:
The way the the project database is updated depends on the type of the project database.
Due to the fact that all items and fields of Jam.py projects have a unique ID attribute, Jam.py very accurately generates sql queries to modify the project database.
While generating sql queries the application currently compares only metadata in the current and imported project. The errors can occur when the application, for example, tries to adds to a table a field that doesn’t exist in the current project metadata but exists in the database table, you created this field outside of Application Builder. This situations can be corrected using Manual mode in Application Builder, see Database, and changing the database.
If you won’t change tables, field and indexes of production database, there will be no problems. Carry out development on the development project and then import its metadata into production.
Note
For the databases that do not support DDL statement rollback (MySql, Oracle) we recommend that you make a backup of the project database and admin.sqlite before performing the import.
Note
For SQLite database, Jam.py doesn’t support importing of metadata into an existing project (project with tables in the database). You can only import metadata into a new project.
Select Roles node in the project tree to create and modify roles that defined users privileges. Each user must be assigned to one of roles defined in the project. A role defines the user’s rights to view, create, modify, and delete data.
To add or delete a role, use New and Delete buttons. To set permissions for a role, select the role in a role list and put or remove a check mark next to the appropriate column by clicking on it with the mouse: View, Create, Edit, Delete (allowed to view, create, modify and delete, respectively).
If the Safe mode checkbox in the project parameters is checked, authentication is needed for a user to work in the system.
But before that, the user must be registered in the framework. To register a user select Users node, click New and fill in the form that appears:
For every item of the project task tree there are two buttons in the upper-right corner of the Application builder : Client module and Server module.
By clicking on these buttons the Code Editor for the client or server module of the item will be opened. (See Working with modules)
To the left of the Editor there is an information pane with four tabs:
To save changes click the OK button or press Ctrl-S.
To search the project modules, click the Find in project button or press Alt-F to display the Find inproject Dialog
Jam.py uses the ace editor editor to implement its code editor.
Hear are keyboard shortcuts for the ace editor.
Select Task node to get to the root of the project task tree.
Press the Edit button in the bottom of the page to change the name and caption of the task.
Use buttons in the right panel of the page to edit
Select the node with the name of the task to get to the groups of the project task tree.
At the bottom of the page there are 3 buttons:
There are groups of three types: Item group, Report group, Table group, see task tree. For each of this group, its own editor will be shown:
Item Group Editor opens when a developer wants to create a new item group or modify an existing one. See Task tree
The upper part of the Item Group Editor have the following fields:
In the center part of the Item Group Editor dialog there is a table containing a list of fields, defined for the item. These fields are common to all items the group will own.
To add, modify or delete a field use the following buttons:
In the bottom-right corner of the Dialog form there are two buttons:
Note
You can create new or modify existing fields and set Primary key field and Deleted flag field attributes only when creating a new group or editing an empty one.
For existing item groups, that already own items you can only change Caption, Name and Visible attributes.
Report Group Editor opens when the developer wants to create a new report group or change an existing report group.
The upper part of the Report Group Editor have the following fields:
In the bottom-right corner of the Dialog form there are two buttons:
Detail Group Editor opens when a developer wants to create a new detail group or modify an existing one. See Task tree
The upper part of the Detail Group Editor have the following fields:
In the center part of the Detail Group Editor dialog there is a table containing a list of fields, defined for the item. These fields are common to all items the group will own.
To add, modify or delete a field use the following buttons:
In the bottom-right corner of the Dialog form there are two buttons:
Note
You can create new or modify existing fields and set Primary key field, Deleted flag field and Master ID field, Master record id field attributes only when creating a new group or editing an empty one.
For existing detail groups, that already own items you can only change Caption, Name and Visible attributes.
Use buttons in the right panel of the page to edit Client and Server modules of a selected group, see
Select a group node in the project tree to get access to items that this group owns, see Task tree.
At the bottom of the page there are 3 buttons:
You can use the up and down arrows to arrange the items in the list. This may be useful for creating a menu or display it in some way on the web page.
The right panel of the page have following buttons:
Item Editor dialog opens when a developer selects a Group node in the project tree of the Application builder and click on the New or Edit button to create a new item or modify a selected one. See Items.
The upper part of the Item Editor dialog have the following fields:
In the center part of the Item Editor dialog there is a table containing a list of fields, defined for the item. To add, modify or delete a field use the following buttons:
In the bottom-right corner of the Dialog form there are two buttons:
Use the Field Editor Dialog to create a new or modify an existing field.
It has two tabs Field, Lookup and Interface.
The Field tab have the following fields:
Note
Please note that Accept attribute is required. Uploaded files are checked on the server against this attribute.
The Edit Fields Dialog opens when a developer selects the item in the Application builder and clicks the Edit Form button.
It has two tabs Layout and Form.
On the Layout tab, you can specify the fields that the user can edit, their order, create tabs and bands for grouping field inputs.
The Layout tab has two lists of fields. The left list contains the fields that were selected for editing. In the right list there are available fields that you can select.
To select a field, select it in the right list and use the Left arrow button in the center or press Space key on a keyboard.
To unselect a field, select it in the left list and use the Right arrow button in the center or press Space key on a keyboard.
To order the selected fields use the buttons that located below left list.
On the right side of the “Layout” tab are the controls that you can use to specify the display options for the fields selected for editing on the form.
You can create tabs and bands and customize fields that you can edit on each tab or band.
On the right side of the tab there are three buttons for adding, editing or deleting tabs of the edit form.
On the left side of the tab there are two buttons for adding and deleting of bands.
Each tab can have several bands.
After creating tabs and bands, you can use field lists and controls on the right to customize the fields that will be edited on each tab and band.
On this tab are the controls that you can use to specify the options of the edit form
Click the OK button to save to result or Cancel to cancel the operation.
After saving, you can see the changes by refreshing the project page.
The View Form Dialog opens when a developer selects the item in the Application builder and clicks the View Form button.
It has two tabs Layout and Form.
On the Layout tab, you can specify how the table is displayed in the view form of the item.
The Layout tab has two lists of fields. The left list contains the fields that were selected be displayed in the table. In the right list there are available fields that you can select.
To select a field, select it in the right list and use the Left arrow button in the center or press Space key on a keyboard.
To unselect a field, select it in the left list and use the Right arrow button in the center or press Space key on a keyboard.
To order the selected fields use the buttons that located below left list.
You can specify the width of the selected columns. To do this, select the field
and enter its width in the Width column. The value can be specified in any
supported CSS unit, for example, in pixels - px
, in percentage, relative to
the parent element - %
. The width specified as an integer value is
interpreted as the width specified in pixels.
Examples of column width values:
On the right side of the “Layout” tab are the controls that you can use to specify the options of the table displayed in the view form:
You can get or change these values programmatically on the client by using the table_options attribute of the item
On this tab are the controls that you can use to specify the options of the view form
You can get or change these values programmatically on the client by using the view_options attribute of the item
Click the OK button to save to result or Cancel to cancel the operation. After saving, you can see the changes by refreshing the project page.
Use Filters Dialog to create and modify item filters. See Filters
To add or edit a filter click on the appropriate button on the form. The following form will appear:
Fill in the following fields:
Help - if any text / html-message is specified, a question mark will be displayed to the right of the input, so when the user moves the mouse pointer over this label, a pop-up window appears displaying this message.
Use the up and down arrows to place the filters in the order in which they will be displayed. See create_filter_inputs
Use this dialog to setup details of an item. See Details.
The Details Dialog has two panels. The left panel lists details that have been added. The right panel have available detail items that could be added as details.
To add a detail item as detail, select it in the right panel and use the Left arrow button in the center or press Space key on a keyboard.
To remove a detail, select it in the left panel and use the Right arrow button in the center or press Space key on a keyboard.
Click the OK button to save to result or Cancel to cancel the operation.
The Order Dialog opens when a developer selects the item in the Application builder (see Items ) and clicks on the Order button to specify how records will be ordered by default. See open method
The Order Dialog has two panels. The left panel lists the fields that have been selected. The right panel have available fields that could be selected.
To select a field, select it in the right panel and use the Left arrow button in the center or press Space key on a keyboard.
To unselect a field, select it in the left panel and use the Right arrow button in the center or press Space key on a keyboard.
To order the selected fields use the buttons that located below left panel.
Click the Desc column to set descending/ascending sorting order for the field.
Click the OK button to save to result or Cancel to cancel the operation.
The Indices Dialog lists the indices that were created for the item table in the project database.
To delete an index click the Delete button. The application will generate the SQL query to drop the index and execute it on the server.
To create a new index click the New button. The following dialog will appear:
Specify the fields to create an index on, by using left and right arrow buttons. Check the Descending checkbox if you want to create a descending index. If necessary, change the name of the index.
Click the OK button to create the index. The application will generate the SQL query to create the index and execute it on the server.
Click Cancel button to cancel the operation.
If an item has a lookup field, and in the definition of lookup item the soft delete attribute is not set, in order to maintain the integrity of the data, we can create a foreign key. See Foreign keys topic in FAQ
To do so click the New button, select the field and click OK.
The Reports Dialog opens when a developer selects the item in the Application builder (see Items ) and clicks on the Order button to specify reports that could printed for the item. A new project code has a function that can be used to print the reports.
The Reports Dialog has two panels. The left panel lists the reports that have been selected. The right panel have available reports that could be selected.
To select a report, select it in the right panel and use the Left arrow button in the center or press Space key on a keyboard.
To unselect a report, select it in the left panel and use the Right arrow button in the center or press Space key on a keyboard.
To order the selected reports use the buttons that located below left panel.
Click the OK button to save to result or Cancel to cancel the operation.
To work with a detail of an item, expand a group node that owns the item and select that item in the tree. In the center of the Application builder all details of this item will be displayed.
The right panel of the page have following buttons:
Use Edit button at the bottom of the page to change item_name
or
caption
of a detail.
Lookup list is a list of integer-text pairs that can used as a datasource for lookup fields.
Note
The length of the lookup list should not exceed 10
Click on the Edit/New buttons to edit/create a lookup list.
Then use the Edit/New buttons to edit/add a lookup pairs to the list.
You can use Jam.py with existing database, that is supported by the framework.
Create a new project with existing database.
If you want to import tables in catalogs or journals groups, delete Common fields:
Select Groups node in the project tree, dbl click corresponding group and delete common fields.
Or create new empty groups.
Select Project node and click Database button. Set DB manual mode to true.
Select group you want to import a table to and click Import button.
In the form that will appear dbl click on the table to import it.
In the Item Editor Dialog check that all fields have valid types. If field type is displayed in the red, try to select appropriate type.
You can import a subset of fields in the table.
Before saving, specify the primary key field for the item and generator name, if necessary.
After saving the imported item, go to the project page and check how it is displayed.
After importing several tables, you can specify lookup fields (in DB manual mode).
Note
Please, do be very careful when performing this operations.
When DB manual mode is removed any changes to the item will be reflected in the corresponding DB table. If you delete the item, the table will be dropped from the database.
Note
The database table to be imported must have a primary key with one field.
Note
Binary fields must not be imported.
Note
This is a new feature, so if you have some comments, suggestions or found some bugs please send a message.
To save change history made by users to must specify the item that will store them.
To do so, open project parameters and click the button to the right of the History item input:
In the dialog that will appear click on the Create history item button
The following mesage will appear when the item will be created:
After that you have to set Keep history attribute of an item to save the history its changes:
To see the history of changes of a record click the icon to the left of the close button on the right part of the header of the edit form.
Or you can do it using the show_history method
Note
Changes are saved when dataset changes are applied to the database using apply method (client/server). Changes to database made with custom SQL requests are not saved in the history.
Note
These changes can significantly increase the size of the database. Please be careful.
In Jam.py, application you can implement a record locking while users concurrently edit a record.
Jam.py uses optimistic locking model, also referred to as optimistic concurrency control.
When an application executes the edit_record method, it receives the current version of the record from the server and saves it. When the user starts saving the record, the server application checks the current version of the record. If it differs from the stored value (another user changed it while the record were being edited), the application warns the user and prohibits saving.
This record locking mechanism is very easy to implement.
To do so you need to create an item that will store record version.
Open project parameters and click the button to the right of the Lock item input:
In the dialog that will appear click on the Create lock item button:
After that you must set Edit lock attribute in the Item Editor Dialog:
Use Language Dialog to add, select and change your language.
Use language locale to set up how the field value will be displayed. See display_text
All language translations are stored in the langs.sqlite database in the “jam” folder in the package.
Note
Therefore, if you made some changes to the translation database and installed a new version of the package, you will use the translation database of this package where there will be no changes made by you.
Please, export you translation to a file!!!
If you want your language translation to be included to Jam.py package, export it to a file and send it me, Andrew Yushev.
Please note that Jam.py is constantly evolving and by submitting your translation you agree to make the necessary changes in the future. If you don’t mind you will be included to the contributors list.
Note
Do not change the following symbols %, %(item)s, %(field)s, %(filters)s
For example
english:
Can’t delete the field %(field)s. It’s used in field definitions:%(fields)s
russian translation:
Нельзя удалить поле %(field)s. Используется в определении полей:%(fields)s
Server side is implemented in Python and uses Werkzeug library, the client side in JavaScript and uses JQuery and Bootstrap
All objects of the framework represent a task tree. Bellow is classes for each kind of task tree objects:
AbstractItem
()¶domain: client
language: javascript
AbstractItem class is the ancestor for all item objects of the task tree
Below the attributes and methods of the class are listed.
ID
¶domain: client
language: javascript
class AbstractItem
The ID attribute is the unique in the framework id of the item
The ID attribute is most useful when referring to the item by number rather than name. It is also used internally.
item_caption
¶domain: client
language: javascript
class AbstractItem
Item_caption attribute specifies the name of the item that appears to users
item_name
¶domain: client
language: javascript
class AbstractItem
Specifies the name of the item as referenced in code. Use item_name to refer to the item in code.
item_type
¶domain: client
language: javascript
class AbstractItem
Specifies the type of the item.
Use the type attribute to get the type of the item. It can have one of the following values:
items
domain: client
language: javascript
class AbstractItem
Lists all items owned by the item.
Use items to access any of the item owned by this object.
Indicates the item that owns this item.
owner
domain: client
language: javascript
class AbstractItem
Use owner to find the owner of an item.
Indicates the root of the task tree that owns this item.
task
domain: client
language: javascript
class AbstractItem
abort
(message)domain: client
language: javascript
class AbstractItem
Use abort method to throw exception.
It can be usefull when you need to abort execution of some ‘on_before’ events.
The following code will throw exception with the text:
execution aborted: invoice_table - a quantity value is required
function on_before_post(item) {
if (item.quantity.value === 0) {
item.abort('a quantity value is required');
}
}
alert
(mess, options)domain: client
language: javascript
class AbstractItem
Use the alert
method to create a pop-up message in the upper-right corner
application that disappears after the first click on the page.
The mess
parameter specifies the text that will be displayed.
The options
parameter is an object with the following attributes:
type
- indicates the type of the message - its font, background color and
header text, if it is not specified in the header parameters.
This must be one of the following:
default value is ‘info’
header
- specifies the header of the alert
pulsate
- if true, the header will pulsate, the default value is true
show_header
- if false, the header will not be displayed.
The methods alert_error
and alert_success
are the same as alert
with the corresponding type
options.
item.alert_error('Failed to send the mail: ' + err);
item.alert('Successfully sent the mail');
can_view
()¶domain: client
language: javascript
class AbstractItem
Use can_view method to determine if a user have a right to get access to an item dataset or to see report generated by report when the project Safe mode parameter is set. If the project Safe mode parameter is not set the method always returns true.
The user privileges are set in the roles node of the project tree.
if (item.visible && item.can_view()) {
$("#submenu")
.append($('<li></li>')
.append(
$('<a href=""></a>')
.text(item.item_caption)
.data('item', item);
)
);
}
each_item
(function(item))¶domain: client
language: javascript
class AbstractItem
Use each_item method to iterate over items owned by this object.
The each_item() method specifies a function to run for each child item (child item is passed as a parameter).
You can break the each_item loop at a particular iteration by making the callback function return false.
The following code will output all catalogs of the project in a browser console:
function on_page_loaded(task) {
task.catalogs.each_item(function(item) {
console.log(item.item_name);
})
}
load_module
(callback)¶domain: client
language: javascript
class AbstractItem
Use load_module method to dynamically load javascript file of an item module, before executing callback.
The method checks whether the module has been loaded, if not, loads the module from the server, initializes the item and then executes the callback function, otherwise just the callback function is executed. The item is passed to the callback function as a parameter.
The request to the sever is executed asyncroniously.
Bellow, the do_some_work function is executed only when an item module has been loaded:
function some_work(item) {
item.load_module(do_some_work);
}
function do_some_work(item) {
// some code
}
load_modules
(module_array, callback)¶domain: client
language: javascript
class AbstractItem
Use load_modules method to dynamically load specified modules before executing the callback.
The method works the same way as load_module, only loads and initializes all modules of items specified in the module_array.
Bellow, the do_some_work function is executed only when modules of the item and its owner has been loaded:
function some_work(item) {
item.load_modules([item, item.owner], do_some_work);
}
function do_some_work(item) {
// some code
}
load_script
(js_filename, callback, onload)¶domain: client
language: javascript
class AbstractItem
Use load_script method to load javascript file from the server, before executing callback.
The method checks whether the file has been loaded, if not, loads it from the server, executes (if specified) onload function and then executes the callback, otherwise just the callback function is executed. The item is passed to the callback function as a parameter.
The js_filename should specify the path to javascript file relative to the server directory.
The request to the sever is executed asyncroniously.
Bellow, the do_some_work function is executed only when lib.js file from server js directory has been loaded.
loaded:
function some_work(item) {
item.load_script('js/lib.js', do_some_work);
}
function do_some_work(item) {
// some code
}
message
(mess, options)domain: client
language: javascript
class AbstractItem
Use message method to create a modal form.
The mess parameter specifies the text or html content that will appear in the body of the form.
The options parameter is an object with the following attrubutes:
The method returns a jquery object of the form. To programmatically close the form pass this object to hide_message method.
The following code will create a yes-no-cancel dialog:
function yes_no_cancel(item, mess, yesCallback, noCallback, cancelCallback) {
var buttons = {
Yes: yesCallback,
No: noCallback,
Cancel: cancelCallback
};
item.message(mess, {buttons: buttons, margin: "20px",
text_center: true, width: 500, center_buttons: true});
}
task.message(
'<a href="http://jam-py.com/" target="_blank"><h3>Jam.py</h3></a>' +
'<h3>Demo application</h3>' +
' with <a href="http://chinookdatabase.codeplex.com/" target="_blank">Chinook Database</a>' +
'<p>by Andrew Yushev</p>' +
'<p>2015</p>',
{title: 'Jam.py framework', margin: 0, text_center: true, buttons: {"OK": undefined},
center_buttons: true}
);
the result of the code above will be:
Creates a modal form with OK, Cancel buttons
question
(mess, yes_callback, no_callback, options)domain: client
language: javascript
class AbstractItem
Use question to create a modal form with Ok and Cancel buttons.
The mess parameter specifies the text or html content that will appear in the body of the form.
If yes_callback, no_callback functions are specified they will be executed when user clicks on the Ok or Cancel button, respectively, and then the form will be closed.
The following code creates a modal form, and delete selected record record when the user clicks the OK button:
item.question('Delete record?',
function() {
item.delete();
}
);
server
(func_name, params, callback)domain: client
language: javascript
class AbstractItem
Use sever
method to execute a function defined in the server module of an item.
Sever
method executes a function with a name func_name
defined in the server
module of an item with parameters specified in params
.
If callback is specified, the function on the server is executed asynchronously,
after which the callback
is executed with parameter that is the result of
the server function execution, otherwise the function is executed synchronously
and returns the result of the server function.
If exception was raised during the operation on the server and the callback parameter is not passed (synchronous execution), the client throws an exception. If the callback parameter is present, it is passed to the callback as parameter.
When exception is raised during the server function execution, the application on the client throws exception with the server exception text.
The first parameter of the function on the server must be item
, it must be
followed by the parameters specified in the function on the client.
params
is a list of parameters. If there are not parameters, the params
can be omitted.
The function defined in the Invoices journal server module:
def get_total(item, id_value):
result = 0;
copy = item.copy()
copy.set_where(id=id_value)
copy.open()
if copy.record_count():
result = copy.total.value
else:
raise Exception, 'Journal "invoices" does not have a record with id %s' % id_value
return result;
the following code in the Invoices journal client module will execute this server function:
task.invoices.server('get_total', [17], function(total, err) {
if (err) {
throw err;
}
else {
console.log(total);
}
});
warning
(mess, callback)domain: client
language: javascript
class AbstractItem
Use warning to create a modal form with the Ok button.
The mess parameter specifies the text or html content that will appear in the body of the form.
If callback function are specified it will be executed when user clicks the button and then the form will be closed.
item.warning('No record selected.');
yes_no_cancel
(mess, yes_callback, no_callback, cancel_callback)¶domain: client
language: javascript
class AbstractItem
Use yes_no_cancel to create a modal form with Yes No, Cancel buttons.
The mess parameter specifies the text or html content that will appear in the body of the form.
If yes_callback, no_callback, cancel_callback functions are specified they will be executed when user clicks on the Yes, No or Cancel button, respectively, and then the form will be closed.
The following code is executed when user clicks on the close button in the upper right corner of an item edit form.
function on_edit_form_close_query(item) {
var result = true;
if (item.is_changing()) {
if (item.is_modified()) {
item.yes_no_cancel('Data has been modified. Save changes?',
function() {
item.apply_record();
},
function() {
item.cancel_edit();
}
);
result = false;
}
else {
item.cancel();
}
}
return result;
}
Task
()¶domain: client
language: javascript
Task class is used to create the root of the Task tree of the project.
Below the attributes, methods and events of the class are listed.
It, as well, inherits attributes and methods of its ancestor class AbstractItem class
forms_container
¶domain: client
language: javascript
class Task
The forms_container
is a JQuery object in which the application will create
forms.
To initialize forms_container
use the
set_forms_container method or the
create_menu method.
The default code uses the create_menu method.
forms_in_tabs
¶domain: client
language: javascript
class Task
If the forms_in_tabs
attribute is set and
forms_container is specified the application will
create forms in tabs.
This attribute can be set in the Interface tab of Parameters.
safe_mode
¶domain: client
language: javascript
class Task
Check the safe_mode
attribute to determine if the
safe mode
parameter of the project is set.
function on_page_loaded(task) {
$("#title").html(task.item_caption);
if (task.safe_mode) {
$("#user-info").text(task.user_info.role_name + ' ' + task.user_info.user_name);
$('#log-out')
.show()
.click(function(e) {
e.preventDefault();
task.logout();
});
}
task.tasks.view($("#content"));
}
templates
domain: client
language: javascript
class Task
The templates
attribute stores the form templates of the project.
user_info
¶domain: client
language: javascript
class Task
Use user_info
attribute to get user information when project
Safe mode parameter
is set.
user_info
is an object that has the following attributes:
user_id
- the user iduser_name
- the user namerole_id
- user role idrole_name
- the role assigned to the useradmin
- if true the user can work in the Application builderIf safe mode is false the user_info
attribute is an empty
object.
function on_page_loaded(task) {
$("#title").html('Jam.py demo application');
if (task.safe_mode) {
$("#user-info").text(task.user_info.role_name + ' ' + task.user_info.user_name);
$('#log-out')
.show()
.click(function(e) {
e.preventDefault();
task.logout();
});
}
// some initalization code
}
add_tab
(container, tab_name, options)¶domain: client
language: javascript
class Task
The add_tab
method creates a tab for a container.
The container
is JQuery object for a container element.
The tab_name
is the name of the tab.
Use can use the options
to specify optional parameters. It is the object that
can have the following attributes:
tab_id
- a unique string identifing the tabshow_close_btn
- if it is set to true
the close tab button will appear
that can be used to close the tabset_active
- if it is set to true
the new tab will became activeon_close
- a callback function that will be called when the close tab button
is clickedThe function returns the JQuery object of the div with tab-pane
class that
will be displayed when tab became active.
The following code will create tabs for editing Customers catalog. It uses create_inputs method:
function on_edit_form_created(item) {
var container = item.edit_form.find('.tabs');
task.init_tabs(container);
item.create_inputs(task.add_tab(container, 'Customer'),
{fields: ['firstname', 'lastname', 'company', 'support_rep_id']}
);
item.create_inputs(task.add_tab(container, 'Address'),
{fields: ['country', 'state', 'address', 'postalcode']}
);
item.create_inputs(task.add_tab(container, 'Contact'),
{fields: ['phone', 'fax', 'email']}
);
}
Below is the edit html template for Customers catalog:
<div class="customers-edit">
<div class="form-body">
<div class="tabs">
</div>
</div>
<div class="form-footer">
<button type="button" id="ok-btn" class="btn btn-ary expanded-btn">
<i class="icon-ok"></i> OK<small class="muted"> [Ctrl+Enter]</small>
</button>
<button type="button" id="cancel-btn" class="btn expanded-btn">
<i class="icon-remove"></i> Cancel
</button>
</div>
</div>
close_tab
(container, tab_id)¶domain: client
language: javascript
class Task
Use the close_tab
method to close tab in the container
identified by
tab_id
.
init_tabs
(container, tabs_position)¶domain: client
language: javascript
class Task
The init_tabs
method initializes tabs for a container.
The container
is JQuery object for a container element.
The tabs_position
parameter specifies where tabs, created by the
add_tab
method will be positioned. It is string that can be one of the following values:
If this parameter is omitted tabs will be positioned at the top of the container.
After this method is called you can use the add_tab method to create tabs.
load
(callback)domain: client
language: javascript
class Task
Load
method loads the project task tree from
the server and initilizes it.
When a Web browser loads the jam.js library in index.html file, jam.js creates an
empty task object. The load
method loads the project
task tree from the server and
initilizes it (see workflow).
After that the application triggers
on_page_loaded event.
The following code is from the project index.html file.
<script src="/jam/js/jam.js"></script>
<script src="/js/events.js"></script>
<script>
$(document).ready(function(){
task.load();
});
</script>
login
(callback)domain: client
language: javascript
class Task
The login
method creates a login form using the login form div defined in the
templates of the index.html file. It is called by the load method
when the project
Safe mode parameter
logout
()domain: client
language: javascript
class Task
Call logout
to logout a user.
function on_page_loaded(task) {
$("#title").html('Jam.py demo application');
if (task.safe_mode) {
$("#user-info").text(task.user_info.role_name + ' ' + task.user_info.user_name);
$('#log-out')
.show()
.click(function(e) {
e.preventDefault();
task.logout();
});
}
// some initalization code
}
set_forms_container
(container, options)¶domain: client
language: javascript
class Task
The set_forms_container
can be used to initialize the
forms_container attribute that will contain forms of
the application.
If the forms_in_tabs attribute is set the applications also initializes the tabs that will be used to display forms.
The container
is JQuery object that will be used as a container for
the application forms.
The options
parameter can have the following attribute:
splash_screen
- an html that will be displayed in the forms_container when
all tabs are closedtask.set_forms_container($("#content"), {
splash_screen: '<h1 class="text-center">Jam.py Demo Application</h1>'
});
upload
(options)domain: client
language: javascript
class Task
Use the upload
method to select a file in the File open dialog box and
upload it to the static/files directory in the server folder.
When saving the file on the server, the file name is changed by the Werkzeug secure_filename function and then the current date is added to it. See http://werkzeug.pocoo.org/docs/0.14/utils/
The options
parameter is an object that may have the following attributes:
callback
- is a callback function that is executed when the file is
downloaded. It is passed, as parameters, the name of the file stored on the
server, the name of the downloaded file and the path to the folder where
the file was saved.show_progress
- if true and the uploaded file is large, the progress bar
will be displayed. the defaul value is trueaccept
- the attribute specifies the types of files that can be submitted
through a file upload, see Accept stringNote
Please note that the accept
attribute specifies only types of files that
can be picked up by the user in the browser.
The server checks all uploaded files for compliance with the Upload file extensions attribute of the Project parameters.
on_edit_form_created(item)
domain: client
language: javascript
class Task class
The on_edit_form_close_query
event is triggered by the
close_edit_form
method of the item.
The item
parameter is the item that triggered the event.
on_edit_form_created(item)
domain: client
language: javascript
class Task class
The on_edit_form_created
event is triggered by the
create_edit_form
method of the item when the form has been created but not shown yet.
The item
parameter is the item that triggered the event.
This event, if defined, is triggered for every item of the task, whose create_edit_form method has been called.
on_edit_form_keydown(item, event)
domain: client
language: javascript
class Task class
on_edit_form_keyup(item, event)
domain: client
language: javascript
class Task class
on_edit_form_shown(item)
domain: client
language: javascript
class Task class
The on_edit_form_shown
event is triggered by the
create_edit_form
method of the item when the form has been shown.
The item
parameter is the item that triggered the event.
This event, if defined, is triggered for every item of the task, whose create_edit_form method has been called.
on_filter_form_close_query(item)
domain: client
language: javascript
class Task class
The on_filter_form_close_query
event is triggered by the
close_filter_form
method of the item.
The item
parameter is the item that triggered the event.
on_filter_form_created(item)
domain: client
language: javascript
class Task class
The on_filter_form_created
event is triggered by the
create_filter_form
method of the item when the form has been created but not shown yet.
The item
parameter is the item that triggered the event.
This event, if defined, is triggered for every item of the task, whose create_filter_form method has been called.
on_filter_form_shown(item)
domain: client
language: javascript
class Task class
The on_filter_form_shown
event is triggered by the
create_filter_form
method of the item when the form has been shown.
The item
parameter is the item that triggered the event.
This event, if defined, is triggered for every item of the task, whose create_filter_form method has been called.
on_page_loaded(task)
domain: client
language: javascript
class Task class
on_param_form_close_query(item)
domain: client
language: javascript
class Task class
The on_param_form_close_query
event is triggered by the
close_param_form
method.
The report
parameter is the report that triggered the event.
on_param_form_created(item)
domain: client
language: javascript
class Task class
The on_param_form_created
event is triggered by the
create_param_form
method, that, usually, is called by then
print
method.
The report
parameter is the report that triggered the event.
on_param_form_shown(item)
domain: client
language: javascript
class Task class
The on_param_form_shown
event is triggered by the
create_param_form
method, that, usually, is called by then
print
method.
The report
parameter is the report that triggered the event.
on_view_form_close_query(item)
domain: client
language: javascript
class Task class
The on_view_form_close_query
event is triggered by the
close_view_form
method of the item.
The item
parameter is the item that triggered the event.
on_view_form_created(item)
domain: client
language: javascript
class Task class
on_view_form_keydown(item, event)
domain: client
language: javascript
class Task class
on_view_form_keyup(item, event)
domain: client
language: javascript
class Task class
on_view_form_shown(item)
domain: client
language: javascript
class Task class
Group
()¶domain: client
language: javascript
Group class is used to create group objects of the task tree
Below the events of the class are listed.
It, as well, inherits attributes and methods of its ancestor class AbstractItem class
on_edit_form_close_query(item)
domain: client
language: javascript
class Group class
The on_edit_form_close_query
event is triggered by the
close_edit_form
method of the item.
The item
parameter is the item that triggered the event.
on_edit_form_created(item)
domain: client
language: javascript
class Group class
The on_edit_form_created
event is triggered by the
create_edit_form
method of the item when the form has been created but not shown yet.
The item
parameter is the item that triggered the event.
This event, if defined, is triggered for every item of the group, whose create_edit_form method has been called.
on_edit_form_keydown(item, event)
domain: client
language: javascript
class Group class
on_edit_form_keyup(item, event)
domain: client
language: javascript
class Group class
on_edit_form_shown(item)
domain: client
language: javascript
class Group class
The on_edit_form_shown
event is triggered by the
create_edit_form
method of the item when the form has been shown.
The item
parameter is the item that triggered the event.
This event, if defined, is triggered for every item of the group, whose create_edit_form method has been called.
on_filter_form_close_query(item)
domain: client
language: javascript
class Group class
The on_filter_form_close_query
event is triggered by the
close_filter_form
method of the item.
The item
parameter is the item that triggered the event.
on_filter_form_created(item)
domain: client
language: javascript
class Group class
The on_filter_form_created
event is triggered by the
create_filter_form
method of the item when the form has been created but not shown yet.
The item
parameter is the item that triggered the event.
This event, if defined, is triggered for every item of the group, whose create_filter_form method has been called.
on_filter_form_shown(item)
domain: client
language: javascript
class Group class
The on_filter_form_shown
event is triggered by the
create_filter_form
method of the item when the form has been shown.
The item
parameter is the item that triggered the event.
This event, if defined, is triggered for every item of the group, whose create_filter_form method has been called.
on_view_form_close_query(item)
domain: client
language: javascript
class Group class
The on_view_form_close_query
event is triggered by the
close_view_form
method of the item.
The item
parameter is the item that triggered the event.
on_view_form_created(item)
domain: client
language: javascript
class Group class
on_view_form_keydown(item, event)
domain: client
language: javascript
class Group class
on_view_form_keyup(item, event)
domain: client
language: javascript
class Group class
on_view_form_shown(item)
domain: client
language: javascript
class Group class
Item
()¶domain: client
language: javascript
Item class is used to create item objects of the task tree that may have an associated database table.
Below the attributes, methods and events of the class are listed.
It, as well, inherits attributes and methods of its ancestor class AbstractItem class
active
domain: client
language: javascript
class Item class
Specifies whether or not an item dataset is open.
Use active
read only property to determine whether an item dataset is open.
The
open
method changes the value of active
to true
. The
close
method sets it to false
.
When the dataset is open its records can be navigated and its data can be modified and the changes saved in the item database table.
active
¶domain: client
language: javascript
class Item class
Set the can_modify
property to false if you need to prohibit changing
of the item in the visual controls.
When can_modify
is true the
can_create,
can_edit,
can_delete
methods return false.
By default the can_modify
property is true.
edit_form
¶domain: client
language: javascript
class Item class
Use edit_form
attribute to get access to a Jquery object representing the
edit form of the item.
It is created by the create_edit_form method.
The
close_edit_form
method sets the edit_form
value to undefined.
In the following example the button defined in the item edit html template is assigned a click event:
item.edit_form.find("#ok-btn").on('click.task',
function() {
item.apply_record();
}
);
edit_options
¶domain: client
language: javascript
class Item class
The edit_options
attribute is a set of options that determine how the edit
form will be displayed on the browser page.
These options are set in the Edit Form Dialog in Application Builder.
You can change edit options
in the
on_edit_form_created
event handler of the item. See example.
edit_options
is an object that has the following attributes:
Option | Description |
---|---|
width | the width of the modal form, the default value is 600 px, |
title | the title of the form, the default value is the value of a item_caption attribute, |
form_border | if true, the border will be displayed around the form |
form_header | if true, the form header will be created and displayed containing form title and various buttons |
history_button | if true and saving change history is enabled, the history button will be displayed in the form header |
close_button | if true, the close button will be created in the upper-right corner of the form |
close_on_escape | if true, pressing on the Escape key will execute the close_edit_form method to close the form |
edit_details | the list of the detail names, that will be available for editing in the edit form, if edit form template contains the div with class ‘edit-detail’ (the default edit form template have this div) |
detail_height | the height of the detail desplayed in the view form, if not specified the height of the detail table is 200px |
fields | specify the list of field names that the create_inputs method will use, if fields attribute of its options parameter is not specified |
template_class | if specified, the div with this class will be searched in the task templates attribute and used as a form html template when creating a form. This attribute must be set before creating the form |
modeless | if set the edit forms will be created modeless, otherwise - modal |
function on_edit_form_created(item) {
item.edit_options.width = 800;
item.edit_options.close_on_escape = false;
}
fields
domain: client
language: javascript
class Item class
function customer_fields(customers) {
customers.open({limit: 1});
for (var i = 0; i < customers.fields.length; i++) {
console.log(customers.fields[i].field_caption, customers.fields[i].display_text);
}
}
filter_form
¶domain: client
language: javascript
class Item class
Use filter_form
attribute to get access to a Jquery object representing the
filter form of the item.
It is created by the create_filter_form method.
The
close_filter_form
method sets the filter_form
value to undefined.
In the following example the button defined in the item filter html template is assigned a click event:
item.filter_form.find("#cancel-btn").on('click',
function() {
item.close_filter()
}
);
filter_options
¶domain: client
language: javascript
class Item class
Use the filter_options
attribute to specify parameters of the modal filter form.
filter_options
is an object that has the following attributes:
width
- the width of the modal form, the default value is 560 px,title
- use it to get or set the title of the filter form,close_button
- if true, the close button will be created in the upper-right
corner of the form, the default value is true,close_caption
- if true and close_button is true, will display ‘Close - [Esc]’
near the buttonclose_on_escape
- if true, pressing on the Escape key will trigger the
close_filter_form
method.close_focusout
- if true, the
close_filter_form
method will be called when a form loses focustemplate_class
- if specified, the div with this class will be searched in
the task
templates
attribute and used as a form html template when creating a formfunction on_filter_form_created(item) {
item.filter_options.width = 700;
}
filtered
domain: client
language: javascript
class Item class
Specifies whether or not filtering is active for a dataset.
Check filtered
to determine whether or not local dataset filtering is in
effect. If filtered
is true
, then filtering is active. To apply filter
conditions specified in the
on_filter_record
event handler, set filtered
to true
.
filters
domain: client
language: javascript
class Item class
function invoices_filters(invoices) {
for (var i = 0; i < invoices.filters.length; i++) {
console.log(invoices.filters[i].filter_caption, invoices.filters[i].value);
}
}
item_state
¶domain: client
language: javascript
class Item
Examine item_state
to determine the current operating mode of the item.
Item_state determines what can be done with data in an item dataset, such as
editing existing records or inserting new ones. The item_state
constantly
changes as an application processes data.
Opening a item changes state from inactive to browse. An application can call edit to put an item into edit state, or call insert or append to put an item into insert state.
Posting or canceling edits, insertions, or deletions, changes item_state
from its current state to browse. Closing a dataset changes its state to
inactive.
To check item_state value use the following methods:
item_state value can be:
item task attribute have consts object that defines following attributes:
so if the item is in edit state can be checked the following way:
item.item_state === 2
or:
item.item_state === item.task.consts.STATE_INSERT
or:
item.is_new()
log_changes
¶domain: client
language: javascript
class Item class
Indicates whether to log data changes.
Use log_changes
to control whether or not changes made to the data in an
item dataset are recorded. When log_changes
is true
(the default), all
changes are recorded. They can later be applied to an application server by
calling the
apply
method. When log_changes
is false, data changes are not recorded and cannot
be applied to an application server.
lookup_field
¶domain: client
language: javascript
class Item class
Use lookup_field
to check if the item was created to select a value for
the lookup field. See Lookup fields
function on_view_form_created(item) {
item.table_options.multiselect = false;
if (!item.lookup_field) {
var print_btn = item.add_view_button('Print', {image: 'icon-print'}),
email_btn = item.add_view_button('Send email', {image: 'icon-pencil'});
email_btn.click(function() { send_email() });
print_btn.click(function() { print(item) });
item.table_options.multiselect = true;
}
}
paginate
domain: client
language: javascript
class Item class
The paginate
attribute determines the behavior of a table created by the
create_table
method
When paginate
is set to true
, a paginator is created, and the table
calculates the number of the rows displayed, based on its height. The table will
internally manipulate the
limit
and offset
parameters of the
open
method, depending on its height and current page, reopening the dataset when
page changes.
If paginate
value is false
, the table will displays all available
records of the dataset.
permissions
domain: client
language: javascript
class Item class
Set the permissions
property attributes to prohibit changing of the item in
the visual controls.
The permissions
property is an object that has the following attributes:
By default theses attributes are set to true.
When these attributes are set to false the corresponding
methods return false.
read_only
¶domain: client
language: javascript
class Item class
Read the read_only
property to determines whether the data can be modified in
data-aware controls.
Set read_only
property to true
to prevent data from being modified in
data-aware controls.
When you assign a value to the read_only property, the application sets the read_only property of all the details and the read_only property of each field to that value.
If the user role prohibits editing of the record, read_only
always returns true
.
In this example we first set read_only
attribute of the invoices item
to true
. It makes all fields and invoice_table detail read only. After that
we allow a user to edit customer field and invoice_table detail.
function on_edit_form_created(item) {
item.read_only = true;
item.customer.read_only = false;
item.invoice_table.read_only = false;
}
rec_count
¶domain: client
language: javascript
class Item class
Read the rec_count
property to get the number of records ownered by
the item’s dataset.
If the module declares an on_filter_record event handler and the Filtered attribute is set, this property calculates the number of records that satisfy this filter, otherwise the record_count method is used to calculate the number of records.
function edit_invoice(invoice_id) {
var invoices = task.invoices.copy();
invoices.open({ where: {id: invoice_id} }, function() {
if (invoices.rec_count) {
invoices.edit_record();
}
else {
invoices.alert_error('Invoices: record not found.');
}
});
}
rec_no
¶domain: client
language: javascript
class Item class
Examine the rec_no
property to determine the record number of the current
record in the item dataset.
rec_no
can be set to a specific record number to position the cursor on that
record.
function calculate(item) {
var subtotal,
tax,
total,
rec;
if (!item.calculating) {
item.calculating = true;
try {
subtotal = 0;
tax = 0;
total = 0;
item.invoice_table.disable_controls();
rec = item.invoice_table.rec_no;
try {
item.invoice_table.each(function(d) {
subtotal += d.amount.value;
tax += d.tax.value;
total += d.total.value;
});
}
finally {
item.invoice_table.rec_no = rec;
item.invoice_table.enable_controls();
}
item.subtotal.value = subtotal;
item.tax.value = tax;
item.total.value = total;
}
finally {
item.calculating = false;
}
}
}
selections
domain: client
language: javascript
class Item class
The selections
attribute stores a list of a primary key field values.
When a Multiple selection check box is checked on the
Layout tab in the
View Form Dialog or
multiselect attribute of the
table_options is set programmatically,
the check box in the leftmost column of the table appears and
each time a user clicks on the check box, the selections
attrubute changes.
It can also be changed programmatically by using add
or remove
methods
or assigning an array.
In this example, the send_email
function, on the client, uses Customers selection
attribute to get array of primary key field values selected by users and send them
to the send_email
function defined in the server module of the item using
the
server
method
function send_email(subject, message) {
var selected = task.customers.selections;
if (!selected.length) {
selected.add(task.customers.id.value);
}
item.server('send_email', [selected, subject, message],
function(result, err) {
if (err) {
item.alert('Failed to send the mail: ' + err);
}
else {
item.alert('Successfully sent the mail');
}
}
);
}
On the server, this array is used to retrieve information about selected customers using open method
import smtplib
def send_email(item, selected, subject, mess):
cust = item.task.customers.copy()
cust.set_where(id__in=selected)
cust.open()
to = []
for c in cust:
to.append(c.email.value)
# code that sends email
table_options
¶domain: client
language: javascript
class Item class
The table_options
attribute is a set of options that determine how the table
of the view form of will be displayed. Options defined in it are used by the
create_table
method if its options parameter don’t override corresponding option.
These options are set in the Layout tab of the View Form Dialog in Application Builder.
You can change table_options
in the
on_view_form_created
event handler of the item. See example.
The table_options
parameter is an object that may have the following attributes:
Option | Description |
---|---|
row_count | specifies the number of rows displayed by the table |
height | if row_count is not specified, it determines the height of the table, the default value is 480. The table at creation calculates the number of rows displayed (row_count), based on the value of this parameter. |
fields | a list of field names. If specified, a column will be created for each field whose name is in this list, if not specified (the default) then the fields attribute of an view_options will be used |
title_line_count | specifies the number of lines of text displayed in a title row, if it is 0, the height of the row is determined by the contents of the title cells |
row_line_count | specifies the number of lines of text displayed in a table row, if it is 0, the height of the row is determined by the contents of the cells |
expand_selected_row | if row_line_count is set and expand_selected_row is greater that 0, it specifies the minimal number of lines of text displayed in the selected row of the table |
title_word_wrap | specifies if the column title text can be wrapped. |
column_width | the width of the columns are calulated by a Web Browser. You can use this option to force the width of columns. The option is an object, key values of which are field names, the values are column widths as CSS units |
editable_fields | the list of field names could be edited in the table. |
selected_field | if editable_fields are set, specifies the name of the field whose column will be selected, when the selected row is changed. |
sortable | if this option is specified, it is possible to sort the records by clicking on the table column header. When a sort_fields option is not specified (default), a user can sort records on any field, otherwise, only on the fields whose names are listed in this option. |
sort_fields | the list of field names on which the table can be sorted, by clicking on the corresponding table column header. If an item is a detail the operation is performed on the client, otherwise sorting is performed on the server (the open method is used internally). |
summary_fields | a list of field names. When it is specified, the table calculates sums for numeric fields and displays them in the table footer, for not numeric fields it displays the number of records. |
freeze_count | an integer value. If it is greater than 0, it specifies number of first columns that become frozen - they will not scroll when the table is scrolled horizontally. |
show_hints | if true, the tooltip will be displayed when the user hovers the mouse over a table cell, and the cell text does not fit in the cell size. The default value is true. |
hint_fields | a list of field names. If it is specified, the tooltip will be displayed only for fields from this list, regardless of the value of show_hints option value. |
on_click | specifies the function, that will be executed when a user click on a table row. The item will be passed as a parameter to the function. |
on_dblclick | specifies the function, that will be executed when a user double click on a table row. The item will be passed as a parameter to the function. |
dblclick_edit | if the value of the option is set to true and the on_dblclick option is not set, the edit form will be shown when a user double click on a table row. |
multiselect | if this option is set, a leftmost column with check-boxes will be created to select records. So, that when a user clicks on the check-box, the value of the primary key field of the record will be added to or deleted from the selections attribute. |
select_all | if true, the menu will appear in the leftmost column of the table header, which will allow the user selects all records that match the current filters and the search value. |
row_callback | the callback functions called each time fields of the record are changed. Two parameters are passed to the function - item, whose record has changed and JQuery object of the corresponding row of the table. Please be carefull - the item passed to the function can be not item itself, but its clone that share the same dataset. |
function on_view_form_created(item) {
item.table_options.row_line_count = 2;
item.table_options.expand_selected_row = 3;
}
The code in the following two examples does the same:
item.invoice_table.create_table(item.view_form.find('.view-detail'), {
height: 200,
summary_fields: ['date', 'total'],
});
item.invoice_table.table_options.height = 200;
item.invoice_table.table_options.summary_fields = ['date', 'total'];
item.invoice_table.create_table(item.view_form.find('.view-detail'));
view_form
¶domain: client
language: javascript
class Item class
Use view_form
attribute to get access to a Jquery object representing the
view form of the item.
It is created by the view method.
The
close_view_form
method sets the view_form
value to undefined.
In the following example the button defined in the item html template is assigned a click event:
item.view_form.find("#new-btn").on('click',
function() {
item.insert_record();
}
);
view_options
¶domain: client
language: javascript
class Item class
The view_options
attribute is a set of options that determine how the view
form of will be displayed on the browser page.
These options are set in the View Form Dialog in Application Builder.
You can change view options in the on_view_form_created event handler of the item. See example.
view_options
is an object that has the following attributes:
Option | Description |
---|---|
width | the width of the modal form, the default value is 600 px |
title | the title of the form, the default value is the value of a item_caption attribute, |
form_border | if true, the border will be displayed around the form |
form_header | if true, the form header will be created and displayed containing form title and various buttons |
history_button | if true and saving change history is enabled, the history button will be displayed in the form header |
refresh_button | if true, the refresh button will be created in the form header, that will allow users to refresh the page by sending request to the server |
enable_search | if true, the search input will be created in the form header |
search_field | the name of the field that will be the default search field |
enable_filters | if true and there are visible filters, the filter button will be created in the form header |
close_button | if true, the close button will be created in the upper-right corner of the form |
close_on_escape | if true, pressing on the Escape key will execute the close_view_form method to close the form |
view_details | the list of detail names, that will be displayed in the view form, if view form template contains the div with class ‘view-detail’ (the default view form template have this div) |
detail_height | the height of the details desplayed in the view form, if not specified the height of the detail table is 200px |
modeless | if true, the form will be displayed as modeless |
template_class | if specified, the div with this class will be searched in the task templates attribute and used as a form html template when creating a form. This attribute must be set before the form is created |
function on_view_form_created(item) {
item.view_options.width = 800;
item.view_options.close_button = false;
item.view_options.close_on_escape = false;
}
virtual_table
¶domain: client
language: javascript
class Item class
Use the read-only virtual_table
property to find out if the item has a
corresponding table in the project database.
If virtual_table
is True
there is no corresponding table in the project
database. You can use these items to work with in-memory dataset or use its
modules to write code.
Calling the
open
method creates an empty data set, and calling the
apply
method does nothing.
domain: client
language: javascript
Use add_edit_button
to dynamically add a button in the edit form.
This method have the same parameters as the add_view_button method
domain: client
language: javascript
Use add_view_button
to dynamically add a button in the view form.
This method is usually used in the on_view_form_created
events.
The following parameters are passed to the method:
text
- the text that will be displayed on the buttonoptions
- options that specify additional properties of the buttonThe options
parameter is an object that may have following attributes:
parent_class_name
is a class name of the parent element, the default value
is ‘form-footer’btn_id
- the id attribute of the buttonbtn_class
- the class of the buttontype
- specifies the type (color) of the button, it can be one of the
following text values:image
- an icon class, one of the icons by Glyphicons from http://getbootstrap.com/2.3.2/base-css.htmlsecondary
: if this attribute is set to true, the button will be right aligned if
Buttons on top attribute of the
View Form Dialog is set, otherwise left aligned.expanded
- if set to true the button will have class ‘expanded-btn’ and
that defines its min-width to 120px, default trueThe method returns a JQuery object of the button.
function on_view_form_created(item) {
var btn = item.add_view_button('Select', {type: 'primary'});
btn.click(function() {
item.select_records('track');
});
}
function on_view_form_created(item) {
if (!item.view_form.hasClass('modal')) {
var print_btn = item.add_view_button('Print', {image: 'icon-print'}),
email_btn = item.add_view_button('Send email', {image: 'icon-pencil'});
email_btn.click(function() { send_email() });
print_btn.click(function() { print(item) });
}
}
append
()domain: client
language: javascript
class Item class
Open a new, empty record at the end of the dataset.
After a call to append, an application can enable users to enter data in the fields of the record, and can then post those changes to the item dataset using post method, and then apply them to the item database table, using apply method.
The append
method
append_record
(container)¶domain: client
language: javascript
class Item class
Open a new, empty record at the end of the dataset and creates an edit_form for visuall editing of the record.
If container
parameter (Jquery object of the DOM element) is specified the
edit form html template is inserted in the container.
If container
parameter is not specified but Modeless form attribute is
set in the
Edit Form Dialog or modeless attribute
of the
edit_options is set programmatically and task has the
forms_in_tabs
attribute set and the application doesn’t have modal forms, the
modeless edit form will be created in the new tab of the
forms_container object of the task.
In all other cases the modal form will be created.
If adding of a record is allowed in modeless mode, the application calls the copy method to create a copy of the item. This copy will be used to append the record.
The append_record
method
apply
(callback, params, async)domain: client
language: javascript
class Item class
Sends all updated, inserted, and deleted records from the item dataset to the application server for writing to the database.
The apply
method can have the following parameters:
callback
: if the parameter is not present and async
parameter is false
or undefined
, the request to the server is
sent synchronously, otherwise, the request is executed asynchronously and
after the response is received, the callback is executedparams
- an object specifying user defined params, that can be used
on the server in the
on_apply
event handler for some additional processingasync
: if its value is true, and callback parameter is missing, the request
is executed asynchronouslyThe order of parameters doesn’t matter.
The apply
method
Note
The server, before writing new records to the database table, generates values for the primary fields. The client updates these fields, based on information received from the server. If you change values of some other fields in the on_apply event handler, these changes will not be reflected on the client. You can update them yourself using, for example, refresh_record method
var self = this;
this.apply(function(err) {
if (err) {
self.alert_error(err);
}
else {
//some code to execute after appling changes
}
});
apply_record
()¶domain: client
language: javascript
class Item class
Writes changes to the application dataset.
The apply_record
method
assign_filters
(item)¶domain: client
language: javascript
class Item class
Use assign_filters
to set filter values of the item to values of filters of
the item
parameter.
function calc_footer(item) {
var copy = item.copy({handlers: false, details: false});
copy.assign_filters(item);
copy.open(
{fields: ['subtotal', 'tax', 'total'],
funcs: {subtotal: 'sum', tax: 'sum', total: 'sum'}},
function() {
var footer = item.view_form.find('.dbtable.' + item.item_name + ' tfoot');
copy.each_field(function(f) {
footer.find('div.' + f.field_name)
.css('text-align', 'right')
.css('color', 'black')
.text(f.display_text);
});
}
);
}
bof
()domain: client
language: javascript
class Item class
Test bof
(beginning of file) method to determine if the cursor is positioned
at the first record in an item dataset.
If bof returns true, the cursor is unequivocally on the first row in the dataset. bof returns true when an application
bof returns false in all other cases.
Note
If both eof and bof return true, the item dataset is empty.
calc_summary
(detail, fields)¶domain: client
language: javascript
class Item class
Use the calc_summary method to calculate sums for fields of a detail and save these values in fields of its master in the on_detail_changed event handler.
The detail
parameter is the detail for the fields of which the sums are
calculated.
The fields
parameter is an object that defines the correspondence between
the master and detail fields. The keys of this object are the master fields,
the values are the corresponding details fields. If the detail field is a numeric
field, its sum is calculated, otherwise the resulting value will be the number
of records. The value of this object can be a function that returns the result
of the calculation for a record of the detail.
function on_detail_changed(item, detail) {
var fields = [
{"total": "total"},
{"tax": "tax"},
{"subtotal": function(d) {return d.quantity.value * d.unitprice.value}}
];
item.calc_summary(detail, fields);
}
can_create
()¶domain: client
language: javascript
class Item
Use can_create
method to determine if a user have a right to create a new
record.
This method takes into account the user permissions set in the roles node in the Application Builder when the project safe mode parameter is set as well as the values of the permissions attribute and the value of can_modify attribute.
if (item.can_create()) {
item.view_form.find("#new-btn").on('click',
function() {
item.append_record();
}
);
}
else {
item.view_form.find("#new-btn").prop("disabled", true);
}
can_delete
()¶domain: client
language: javascript
class Item
Use can_delete
method to determine if a user have a right to delete a record of
an item dataset.
This method takes into account the user permissions set in the roles node in the Application Builder when the project safe mode parameter is set as well as the values of the permissions attribute and the value of can_modify attribute.
if (item.can_delete()) {
item.view_form.find("#delete-btn").on('click',
function() {
item.delete_record();
}
);
}
else {
item.view_form.find("#delete-btn").prop("disabled", true);
}
can_edit
()¶domain: client
language: javascript
class Item
Use can_edit
method to determine if a user have a right to edit a record of
an item dataset.
This method takes into account the user permissions set in the roles node in the Application Builder when the project safe mode parameter is set as well as the values of the permissions attribute and the value of can_modify attribute.
if (item.can_edit()) {
item.view_form.find("#edit-btn").on('click',
function() {
item.edit_record();
}
);
}
else {
item.view_form.find("#edit-btn").prop("disabled", true);
}
cancel
()domain: client
language: javascript
class Item class
Call cancel
to undo modifications made to one or more fields belonging to
the current record, as long as those changes are not already posted to the item
dataset.
Cancel
cancel_edit
()¶domain: client
language: javascript
class Item class
Cancel visual editing on the record
The cancel_edit
method
clear_filters
()¶domain: client
language: javascript
class Item class
Use clear_filters
to set filter values of the item to null
.
clone
(keep_filtered)domain: client
language: javascript
class Item class
Use the clone method to create a copy of an item that shares with it its dataset. The clone item has its own cursor, so you can navigate it and the cursor position of the item doesn’t change.
Set the keep_filtered
parameter to true if you want the clone to have the same
local filter as the item.
function calc_sum(item) {
var clone = item.clone(),
result = 0;
clone.each(function(c) {
result += c.sum.value;
})
return result;
}
close
()domain: client
language: javascript
class Item class
close_edit_form
()¶domain: client
language: javascript
class Item class
Use close_edit_form
method to close the edit form of the item.
The close_edit_form
method triggers the
on_edit_form_close_query
event handler of the item, if one is defined. If the event handler is defined
and
true
- the form is destroyed, the item’s
edit_form
atrribute is set to undefined and the methods exitsfalse
- the operation is aborted and the methods exits,If it don’t return a value (undefined) the method triggers the on_edit_form_close_query of the group that owners the item, if one is defined for the group. If this event handler is defined and
true
- the form is destroyed, the item’s
edit_form
atrribute is set to undefined and the methods exitsfalse
- the operation is aborted and the methods exits,If it don’t return a value (undefined) the method triggers the on_edit_form_close_query of the task. If this event handler is defined and
true
- the form is destroyed, the item’s
edit_form
atrribute is set to undefined and the methods exitsfalse
- the operation is aborted and the methods exits,If no event handler is defined or none of these event handlers return false
, the
form is destroyed and the item’s
edit_form
atrribute is set to undefined.
close_filter_form
()¶domain: client
language: javascript
class Item class
Use close_filter_form
method to close the filter form of the item.
The close_filter_form
method triggers the
on_filter_form_close_query
event handler of the item, if one is defined. If the event handler is defined
and
``false``
- the form is destroyed, the item’s
filter_form
atrribute is set to undefined and the methods exitsfalse
- the operation is aborted and the methods exits,If it don’t return a value (undefined) the method triggers the on_filter_form_close_query of the group that owners the item, if one is defined for the group. If this event handler is defined and
true
- the form is destroyed, the item’s
filter_form
atrribute is set to undefined and the methods exitsfalse
- the operation is aborted and the methods exits,If it don’t return a value (undefined) the method triggers the on_filter_form_close_query of the task. If this event handler is defined and
true
- the form is destroyed, the item’s
filter_form
atrribute is set to undefined and the methods exitsfalse
- the operation is aborted and the methods exits,If no event handler is defined or none of these event handlers return false
, the
form is destroyed and the item’s
filter_form
atrribute is set to undefined.
close_view_form
()¶domain: client
language: javascript
class Item class
Use close_view_form
method to close the view form of the item.
The close_view_form
method triggers the
on_view_form_close_query
event handler of the item, if one is defined. If the event handler is defined
and
true
- the form is destroyed, the item’s
view_form
atrribute is set to undefined and the methods exitsfalse
- the operation is aborted and the methods exits,If it don’t return a value (undefined) the method triggers the on_view_form_close_query of the group that owners the item, if one is defined for the group. If this event handler is defined and
true
- the form is destroyed, the item’s
view_form
atrribute is set to undefined and the methods exitsfalse
- the operation is aborted and the methods exits,If it don’t return a value (undefined) the method triggers the on_view_form_close_query of the task. If this event handler is defined and
true
- the form is destroyed, the item’s
view_form
atrribute is set to undefined and the methods exitsfalse
- the operation is aborted and the methods exits,If no event handler is defined or none of these event handlers return false
, the
form is destroyed and the item’s
view_form
atrribute is set to undefined.
copy
(options)domain: client
language: javascript
class Item class
Use copy to create a copy of an item. The created copy is not added to the task tree and will be destroyed by JavaScript garbage collection process when no longer needed.
All attributes of the copy object are defined as they were at the time of loading of the task tree when application started. See Workflow
Options
parameter further specifies the created copy. It can have the
following attributes:
handlers
- if the value of this attribute is true
, all the settings
to the item made in the Form Dialogs in the Application Builder and all the
functions and events defined in the client module of the item will also be
available in the copy. The default value is true
.filters
- if the value of this attribute is true
, the filters will be
created for the copy, otherwise there will be no filters. The default value
is true
.details
- if the value of this attribute is true
, the details will be
created for the copy, otherwise there will be no details. The default value
is true
.The following code is used in the Demo project to asynchronously calculate total values of the fields, displayed at the foot of the Invoice journal table:
function on_filter_applied(item) {
var copy = item.copy({handlers: false, details: false});
copy.assign_filters(item);
copy.open(
{fields: ['subtotal', 'tax', 'total'],
funcs: {subtotal: 'sum', tax: 'sum', total: 'sum'}},
function() {
var footer = item.view_form.find('.dbtable.' + item.item_name + ' tfoot');
copy.each_field(function(f) {
footer.find('div.' + f.field_name)
.css('text-align', 'right')
.css('color', 'black')
.text(f.display_text);
});
}
);
}
create_detail_views
(container)¶domain: client
language: javascript
Use create_detail_views
to create view froms of the details of the item.
These details can be specified in the Edit details attribute of the
Edit Form Dialog or
set in the edit_details
attribute of the
edit_options.
This method is usually used in the on_edit_form_created
event handler.
The following parameters are passed to the method:
container
- a JQuery object that will contain view form of the details, if
there is no container, the method returns.If there is more than one detail, the method creates view forms in tabs.
If details are not active , the method calls their open method.
function on_edit_form_created(item) {
item.edit_form.find("#cancel-btn").on('click.task', function(e) { item.cancel_edit(e) });
item.edit_form.find("#ok-btn").on('click.task', function() { item.apply_record() });
if (!item.master && item.owner.on_edit_form_created) {
item.owner.on_edit_form_created(item);
}
if (item.on_edit_form_created) {
item.on_edit_form_created(item);
}
item.create_inputs(item.edit_form.find(".edit-body"));
item.create_detail_views(item.edit_form.find(".edit-detail"));
return true;
}
create_edit_form
(container)¶domain: client
language: javascript
class Item class
Use create_edit_form
method to create an edit form of the item for visual
editing of a record.
The method searches for an item html template in the task templates attribute (See Forms ), creates a clone of the template and assigns it to the item edit_form attribute.
If container
parameter is specified the method empties it and appends the
html template to it. Otherwise it creates a modal form and appends the
html to it.
Triggers the on_edit_form_created of the task.
Triggers the on_edit_form_created of the group that owners the item, if one is defined for the group.
Triggers the on_edit_form_created of the item, if one is defined.
Assigns the JQuery keyup and keydown events to the edit_form so
that when an JQuery event of the window occurs, the on_edit_form_keyup
and
on_edit_form_keydown
events are triggered. They are triggered (if defined)
in the same way: first the task event handler, the group event handler and
then the event handler of the item itself. After that the JQuery stopPropagation
method of the event is called.
If the form is modal, shows it. Before showing the form the method applies options specidied in the edit_options attribute.
Triggers the on_edit_form_shown of the task.
Triggers the on_edit_form_shown of the group that owners the item, if one is defined for the group.
Triggers the on_edit_form_shown of the item, if one is defined.
create_filter_form
(container)¶domain: client
language: javascript
class Item class
Use create_filter_form
method to create an filter form of the item for visual
editing item filters.
The method searches for an item html template in the task templates attribute (See Forms ), creates a clone of the template and assigns it to the item filter_form attribute.
If container
parameter is specified the method empties it and appends the
html template to it. Otherwise it creates a modal form and appends the
html to it.
Triggers the on_filter_form_created of the task.
Triggers the on_filter_form_created of the group that owners the item, if one is defined for the group.
Triggers the on_filter_form_created of the item, if one is defined.
Assigns the JQuery keyup and keydown events to the filter_form so
that when an JQuery event of the window occurs, the on_filter_form_keyup
and
on_filter_form_keydown
events are triggered. They are triggered (if defined)
in the same way: first the task event handler, the group event handler and
then the event handler of the item itself. After that the JQuery stopPropagation
method of the event is called.
If the form is modal, shows it. Before showing the form the method applies options specidied in the filter_options attribute.
Triggers the on_filter_form_shown of the task.
Triggers the on_filter_form_shown of the group that owners the item, if one is defined for the group.
Triggers the on_filter_form_shown of the item, if one is defined.
create_filter_inputs
(container, options)¶domain: client
language: javascript
Use create_filter_inputs
to create data-aware visual controls (inputs,
cheboxes) for editing
filters
of an item.
This method is usually used in on_filter_form_created
events triggered by
create_filter_form
method.
The following parameters are passed to the method:
container
- a JQuery object that will contain visual controls, if
container length is 0 (no container), the method returns.options
- options that specify how controls are displayedThe options
parameter is an object that may have following attributes:
filters
- a list of filter names. If specified, a visual control will be
created for each filter whose name is in this list, if not specified
(the default) then the fields attribute of an
filter_options
will be used (by default it lists all visible filters specified in the
Application builder),col_count
- the number of columns that will be created for visual controls,
the default value is 1.label_on_top
: the default value is false. If this value is false, the
labels are placed to the left of controls, otherwise the are created above the
controlstabindex
- if tabindex is specified, it will the tabindex of the first
visual control, tabindex of all subsequent controls will be increased by 1.autocomplete
- the default value is false. If this attribute is set to
true, the autocomplete attribute of controls is set to “on”Before creating controls the application empties the container.
function on_filter_form_created(item) {
item.filter_options.title = item.item_caption + ' - filter';
item.create_filter_inputs(item.filter_form.find(".edit-body"));
item.filter_form.find("#cancel-btn").on('click.task', function() {
item.close_filter()
});
item.filter_form.find("#ok-btn").on('click.task', function() {
item.apply_filter()
});
}
create_inputs
(container, options)¶domain: client
language: javascript
Use create_inputs
to create data-aware visual controls (inputs, checkboxes)
for editing
fields
of the item.
This method is usually used in the on_edit_form_created
events.
The following parameters are passed to the method:
container
- a JQuery object that will contain visual controls, if
container length is 0 (no container), the method returns.options
- options that specify how controls are displayedThe options
parameter is an object that may have following attributes:
fields
- a list of field names. If specified, a visual control will be
create for each field whose name is in this list, if not specified then the
fields
attribute of
edit_options
will be used (if defined), otherwise the layout, created in the
Edit Form Dialog of
Application builder, will be createdcol_count
- the number of columns that will be created for visual controls,
the default value is 1,Before creating controls the application empties the container.
function on_edit_form_created(item) {
item.create_inputs(item.edit_form.find(".left-div"),
{fields: ['firstname', 'lastname', 'company', 'support_rep_id']}
);
}
create_table
(container, options)¶domain: client
language: javascript
Use create_table
method to create a table that displays records of
the item dataset.
The behavior of the table is determined by the paginate attribute of the item.
When paginate is true, a paginator will be created, that will internally update the item dataset when the page is changed.
If the value of paginate is false, all available records of the item dataset will be displayed in the table.
The table, created by this method is data aware, when you change the dataset, these changes are immediately reflected in the table. So you can create a table and then call the open method.
The following parameters could be passed to the method:
container
- a JQuery object that will contain the table, if
container length is 0 (no container), the method returns. Before creating
the table the application empties the container.options
- options that specify the way the table will be displayed.
By default, the method uses the
table_options that are set in the
View Form Dialog in Application
Builder when creating the table. The options
attributes take precedence over the
table_options attributes.The options
parameter is an object that may have the same attributes as
table_options.
function on_edit_form_created(item) {
item.edit_options.width = 1050;
item.invoice_table.create_table(item.edit_form.find(".edit-detail"),
{
height: 400,
editable_fields: ['quantity'],
column_width: {"track": "60%"}
});
}
create_view_form
(container)¶domain: client
language: javascript
class Item class
Use create_view_form
method to create a view form of the item.
Then it searches for an item html template in the task templates attribute (See Forms ) and creates a clone of the template and assigns it to the item view_form attribute.
If container
parameter is specified the method empties it and appends the
html template to it. Otherwise it creates a modal form and appends the
html to it.
Triggers the on_view_form_created of the task.
Triggers the on_view_form_created of the group that owners the item, if one is defined for the group.
Triggers the on_view_form_created of the item, if one is defined.
Assigns the JQuery keyup and keydown events to the view_form so
that when an JQuery event of the window occurs, the on_view_form_keyup
and
on_view_form_keydown
events are triggered. They are triggered (if defined)
in the same way: first the task event handler, the group event handler and
then the event handler of the item itself. After that the JQuery stopPropagation
method of the event is called.
If the form is modal, shows it. Before showing the form the method applies options specidied in the view_options attribute.
Triggers the on_view_form_shown of the task.
Triggers the on_view_form_shown of the group that owners the item, if one is defined for the group.
Triggers the on_view_form_shown of the item, if one is defined.
delete
()domain: client
language: javascript
class Item class
Deletes the active record and positions the cursor on the next record.
The delete
method
delete_record
()¶domain: client
language: javascript
class Item class
disable_controls
()¶domain: client
language: javascript
class Item class
Call disable_controls
to “turn off” data-aware controls, so they will not refrect
changes to the item dataset data.
Call enable_controls to re-enable data display in data-aware controls associated with the dataset and update values they display.
function calculate(item) {
var subtotal,
tax,
total,
rec;
if (!item.calculating) {
item.calculating = true;
try {
subtotal = 0;
tax = 0;
total = 0;
item.invoice_table.disable_controls();
rec = item.invoice_table.rec_no;
try {
item.invoice_table.each(function(d) {
subtotal += d.amount.value;
tax += d.tax.value;
total += d.total.value;
});
}
finally {
item.invoice_table.rec_no = rec;
item.invoice_table.enable_controls();
}
item.subtotal.value = subtotal;
item.tax.value = tax;
item.total.value = total;
}
finally {
item.calculating = false;
}
}
}
disable_edit_form
()¶domain: client
language: javascript
class Item class
Call disable_edit_form
to prevent any user actions when
edit_form is visible.
Call enable_edit_form to re-enable edit form.
function on_edit_form_created(item) {
var save_btn = item.add_edit_button('Save and continue');
save_btn.click(function() {
if (item.is_changing()) {
item.disable_edit_form();
item.post();
item.apply(function(error){
if (error) {
item.alert_error(error);
}
item.edit();
item.enable_edit_form();
});
}
});
}
each
(function(item))domain: client
language: javascript
class Item class
Use each method to iterate over records of an item dataset.
The each() method specifies a function to run for each record.
You can break the each loop at a particular iteration by making the callback
function return false
.
In the example below the t and item.invoice_table are pointers to the same object:
var subtotal = 0,
tax = 0,
total = 0;
item.invoice_table.each(function(t) {
subtotal += t.amount.value;
tax += t.tax.value;
total += t.total.value;
});
each_detail
(function(detail))¶domain: client
language: javascript
class Item class
Use each method to iterate over details of an item.
The each_detail() method specifies a function to run for each detail of the item (the current detail is passed as a parameter).
You can break the each_detail loop at a particular iteration by making the callback
function return false
.
each_field
(function(field))¶domain: client
language: javascript
class Item class
Use each_field method to iterate over fields owned by an item.
The each_field() method specifies a function to run for each field (the current field is passed as a parameter).
You can break the each_field loop at a particular iteration by making the callback
function return false
.
function customer_fields(customers) {
customers.open({limit: 1});
customers.each_field(function(f) {
console.log(f.field_caption, f.display_text);
});
}
each_filter
(function(filter))¶domain: client
language: javascript
class Item class
Use each_filter method to iterate over filters owned by an item.
The each_filter() method specifies a function to run for each filter (the current filter is passed as a parameter).
You can break the each_filter loop at a particular iteration by making the callback
function return false
.
function customer_filters(customers) {
customers.each_filter(function(f) {
console.log(f.filter_caption, f.value);
});
}
edit
()domain: client
language: javascript
class Item class
Enables editing of data in the dataset.
After a call to edit, an application can enable users to change data in the fields of the record, and can then post those changes to the item dataset using post method, and then apply them to database using apply method.
The edit
method
edit_record
(container)¶domain: client
language: javascript
class Item class
Puts the current record in edit state and creates an edit_form for visual editing of the record.
If container
parameter (Jquery object of the DOM element) is specified the
edit form html template is inserted in the container.
If container
parameter is not specified but Modeless form attribute is
set in the
Edit Form Dialog or modeless attribute
of the
edit_options is set programmatically and task has the
forms_in_tabs
attribute set and the application doesn’t have modal forms, the
modeless edit form will be created in the new tab of the
forms_container object of the task.
In all other cases the modal form will be created.
If editing is allowed in modeless mode, the user can edit several records at the same time. In this case the application calls the copy method to create a copy of the item. This copy will be used to edit the record. The application will call its open method to get the record from the server by using the value of the primary key field as a filter.
In case of modal editing the application executes refresh_record methods to get from the server the latest data of the record.
If a record locking is enabled for the item, along with receiving the record data from the server the application receives the version of the record.
Then the edit_record
method
enable_controls
()¶domain: client
language: javascript
class Item class
Call enable_controls
to permit data display in data-aware controls and
and redraw them after a prior call to
disable_controls.
enable_edit_form
()¶domain: client
language: javascript
class Item class
Call enable_edit_form
to re-enable edit form after prior call to
disable_edit_form
function on_edit_form_created(item) {
var save_btn = item.add_edit_button('Save and continue');
save_btn.click(function() {
if (item.is_changing()) {
item.disable_edit_form();
item.post();
item.apply(function(error){
if (error) {
item.alert_error(error);
}
item.edit();
item.enable_edit_form();
});
}
});
}
eof
()domain: client
language: javascript
class Item class
Test eof
(end-of-file) to determine if the cursor is positioned at the last
record in an item dataset. If eof returns true
, the cursor is unequivocally on
the last row in the dataset. eof returns true
when an application:
eof returns false in all other cases.
Note
If both eof and bof return true
, the item dataset is empty.
field_by_name
(field_name)¶domain: client
language: javascript
class Item class
Call field_by_name
to retrieve field information for a field when only its
name is known.
The field_name
parameter is the name of an existing field.
field_by_name
returns the field object for the specified field. If the
specified field does not exist, field_by_name
returns null
.
filter_by_name
(filter_name)¶domain: client
language: javascript
class Item class
Call filter_by_name
to retrieve filter information for a filter when only its
name is known.
The filter_name
parameter is the name of an existing filter.
filter_by_name
returns the filter object for the specified filter. If the
specified filter does not exist, filter_by_name
returns null
.
first
()domain: client
language: javascript
class Item class
Call first
to position the cursor on the first record in the item dataset and
make it the active record. First
posts any changes to the active record.
insert
()domain: client
language: javascript
class Item class
Inserts a new, empty record in the item dataset.
After a call to insert
, an application can enable users to enter data in the
fields of the record, and can then post those changes to the item dataset using
post
method, and then apply them to the item database table, using
apply
method.
The insert
method
insert_record
(container)¶domain: client
language: javascript
class Item class
Open a new, empty record at the beginning of the dataset and creates an edit_form for visuall editing of the record.
If container
parameter (Jquery object of the DOM element) is specified the
edit form html template is inserted in the container.
If container
parameter is not specified but Modeless form attribute is
set in the
Edit Form Dialog or modeless attribute
of the
edit_options is set programmatically and task has the
forms_in_tabs
attribute set and the application doesn’t have modal forms, the
modeless edit form will be created in the new tab of the
forms_container object of the task.
In all other cases the modal form will be created.
If insertion of a record is allowed in modeless mode, the application calls the copy method to create a copy of the item. This copy will be used to insert the record.
The insert_record
method
is_changing
()¶domain: client
language: javascript
class Item class
Checks if an item is in edit or insert state and returns true
if it is.
An application calls edit to put an item into edit state and append or insert to put an item into insert state.
is_edited
()¶domain: client
language: javascript
class Item class
Checks if an item is in edit state and returns true
if it is.
An application calls edit to put an item into edit state.
is_modified
()¶domain: client
language: javascript
class Item class
Checks if the current record of an item dataset has been modified during edit or
insert opertaions. The method returns false
after the
post method is executed.
is_new
()¶domain: client
language: javascript
class Item class
Checks if an item is in insert state and returns true
if it is.
An application calls append or insert methods to put an item into insert state.
last
()domain: client
language: javascript
class Item class
Call last
to position the cursor on the last record in the item dataset and
make it the active record.
locate
(fields, values)domain: client
language: javascript
class Item class
Implements a method for searching an item dataset for a specified record and makes that record the active record.
Arguments:
fields
: a field name, or list of field namesvalues
: a field value of list of field valuesThis method locates the record where the fields specified by fields
parameter
have the values specified by values
parameter.
Locate
returns true if a record is found that matches the specified criteria and
the cursor repositioned to that record.
If a matching record was not found and the cursor is not repositioned, this method returns false.
next
()domain: client
language: javascript
class Item class
Call next
to position the cursor on the next record in the item dataset and
make it the active record. Next posts any changes to the active record.
open
(options, callback, async)domain: client
language: javascript
class Item class
Call open
to sends a request to the server for obtaining an item dataset.
The open
method can have the following parameters:
options
- an object that specifies the parameters of the request sent to
the servercallback
: if the parameter is not present, the request is sent to the
server synchronously, otherwise, the request is executed asynchronously and
after the dataset is received, the callback is executedasync
: if its value is true, and callback parameter is missing, the request
is executed asynchronouslyThe order of parameters doesn’t matter.
The method initializes the item
fields,
formulates parameters of a request, based on the options
and triggers the
on_before_open event
handler if one is defined for the item.
After that it sends the request to the server. If callback
parameter-function is specified, the request is executed asynchronously,
otherwise - synchronouslly.
The server, after recieving the request, checks if the corresponding item on the server (item of the task tree with the same ID attribute) has the on_open event handler. If so it executes this event handler and returns the result of the execution to the client, otherwise generates a SELECT SQL query, based on parameters of the request, executes this query and returns the result to the client.
The client, after receiving the result of the request, changes its dataset and sets active to true, the item_state to browse mode, goes to the first record of the dataset, triggers on_after_open and on_filters_applied event handlers (if they are defined for the item), and updates controls.
Then it calls callback
function if it was specified.
The options
object parameter can have the following attributes:
expanded
- if the value of this attribute is true, the SELECT query, generated
on the server, will have JOIN clauses to get lookup values of the
lookup fields
, otherwise no lookup values will be returned. The default value if true
.fields
- use this parameter to specify the WHERE clause of the SELECT
query. This parameter is a list of field names. If it is omitted, the fields
defined by the
set_fields
method will be used. If the
set_fields
method was not called before the open
method execution, all the fields
created by a developer will be used.where
- use this parameter to specify how records will be filtered in the
SQL query. This parameter is an object of key-value pairs, where keys are
field names, that are followed, after double underscore, by a filtering symbols
(see
Filtering records
). If this parameter is omitted, values
defined by the
set_where
method will be used. If the
set_where
method was not called before the open
method execution, and where
parameter is omitted, then the values of
filters
defined for the item will be used to filter records.order_by
- use order_by
to specify sort order of the records. This
parameter is a list of field names. If there is a sign ‘-’ before the field
name, then on this field records will be sorted in decreasing order. If this
parameter is omitted, a list defined by the
set_order_by
method will be used.offset
- use offset
to specify the offset of the first row to return.limit
- use limit
to limit the output of a SQL query to the first
so-many rows.funcs
- this parameter can be a an object of key-value pairs, where key is
a field name and value is function name that will be applied to the field in
the SELECT Querygroup_by
- use group_by
to specify fields to group the result of the
query by. This parameter must be a list of field names.open_empty
- if this parameter is set to true
, the application does
not send a request to the server but just initializes an empty dataset.
The default value if false
.params
- use the parameter to pass some user defined options to be used in
the
on_open
event handler on the server. This parameter must be an object of key-value pairsNote
When the
paginate
attribute of the item is set to true
and a table is created by the
create_table
method, the limit
and offset
parameters are set internally by the
table depending on its row number and current page.
function get_customer_sales(task, customer_id) {
var date1 = new Date(new Date().setYear(new Date().getFullYear() - 5)),
date2 = new Date(),
invoices = task.invoices.copy();
invoices.open({
fields: ['customer', 'invoicedate', 'total'],
where: {customer: customer_id, invoicedate__ge: date1, invoicedate__le: date2},
order_by: ['invoicedate']
});
}
function get_customer_sales(task, customer_id) {
var date1 = new Date(new Date().setYear(new Date().getFullYear() - 5)),
date2 = new Date(),
invoices = task.invoices.copy();
invoices.set_fields(['customer', 'invoicedate', 'total']);
invoices.set_where({customer: customer_id, invoicedate__ge: date1, invoicedate__le: date2});
invoices.set_order_by(['invoicedate']);
invoices.open();
}
function get_sales(task) {
var sales = task.invoices.copy();
sales.open({
fields: ['customer', 'id', 'total'],
funcs: {'id': 'count', 'total': 'sum'},
group_by: ['customer'],
order_by: ['customer']
});
}
post
()domain: client
language: javascript
class Item class
Writes a modified record to the item dataset. Call post to save changes made to a record after append, insert or edit method was called.
The post
method
prior
()domain: client
language: javascript
class Item class
Call prior
to position the cursor on the previous record in the item dataset and
make it the active record. last posts any changes to the active record.
record_count
()¶domain: client
language: javascript
class Item class
Call record_count
to get the total number of records ownered by the item’s
dataset.
item.open()
if (item.record_count()) {
// some code
}
refresh_page
(callback, async)¶domain: client
language: javascript
class Item class
Call refresh_page
to send to the server a request to get current data of
the current page and refresh existing visual controls.
The refresh_page
method can have the following parameters:
callback
: if the parameter is not present, the request is sent to the
server synchronously, otherwise, the request is executed asynchronously and
after that the callback is executedasync
: if its value is true, and callback parameter is missing, the request
is executed asynchronouslyrefresh_record
(options, callback, async)¶domain: client
language: javascript
class Item class
Call refresh_record
to send to the server a request to get current data of
the current record and refresh existing visual controls.
The refresh_record
method can have the following parameters:
callback
: if the parameter is not present, the request is sent to the
server synchronously, otherwise, the request is executed asynchronously and
after that the callback is executedasync
: if its value is true, and callback parameter is missing, the request
is executed asynchronouslyoptions
- an object that can have an attribute
details
- a list of item_names of details the item. These details are
refreshed too.The order of the parameters does not matter
search
(field_name, value, search_type, callback)domain: client
language: javascript
class Item class
Call search
to send to the server a request to generate and execute
an sql query to get all records which satisfy the search condition for the field.
The query will also satisfy currently set filteres or where condition for an item.
The existing visual controls will be update with the returned dataset.
Parameters:
field_name
- name of the fieldvalue
- value of the conditionsearch_type
- type of search as a string, see Filter symbol in
Filtering recordscallback
- a callback function that will be executed after search is
executedselect_records
(field_name, all_records)¶domain: client
language: javascript
class Item class
Use the select_records method to add records to an item by selecting them from the lookup item of a field.
For example, this method is used in the Demo application to add tracks to an invoice by selecting them from Tracks catalog.
Parameters:
field_name
parameter is a field name of a lookup field of the itemall_records
parameter is set to true, all selected records are added,
otherwise the method omits existing records (they were selected earlier).function on_view_form_created(item) {
var btn = item.add_view_button('Select', {type: 'primary'});
btn.click(function() {
item.select_records('track');
});
}
set_fields
(field_list)¶domain: client
language: javascript
class Item class
Use the set_fields
method to define and store internally the fields
option that will be used by the
open
method, when its own fields
option is not specified.
After the open method executes it clears this internally stored value.
The field_list
parameter is a list of field names.
The result of the execution of following code snippets wil be the same:
item.open({fields: ['id', 'invoicedate']});
item.set_fields(['id', 'invoicedate']);
item.open();
set_order_by
(field_list)¶domain: client
language: javascript
class Item class
Use the set_order_by
method to define and store internally the order_by
option that will be used by the
open
method, when its own order_by
option is not specified. The
open
method clears internally stored parameter value.
The field_list
parameter is a list of field names. If there is a sign ‘-’
before a field name, then on this field records will be sorted in decreasing
order.
The result of the execution of following code snippets wil be the same:
item.open({order_by: ['-invoicedate']});
item.set_order_by(['-invoicedate']);
item.open();
set_where
(where)¶domain: client
language: javascript
class Item class
Use the set_where
method to define and store internally the where
option that will be used by the
open
method, when its own where
option is not specified. The
open
method clears internally stored parameter value.
The where
parameter is an object of key-value pairs, where keys are
field names, that are followed, after double underscore, by a filtering symbols
(see
Filtering records
).
The result of the execution of following code snippets wil be the same:
item.open({where: {id: 100}});
item.set_where({id: 100});
item.open();
show_history
()¶domain: client
language: javascript
class Item class
Class show_history method of am item to open a dialog displaying history of changes of the selected record
update_controls
()¶domain: client
language: javascript
class Item class
Call update_controls to tell associated controls to redraw to reflect current data.
view
(container)domain: client
language: javascript
class Item class
Use view
method to create a view form of the item.
The method check if the javascript modules of the item and its owner are loaded, and if not (the Dynamic JS modules loading parameter of the project is set) then loads them.
If container
parameter (Jquery object of the DOM element) is specified the
view form html template is inserted in the container.
If the init_tabs method of the task is called for this conainer the tab is created for this form.
After that it calls the create_view_form method
In the following code the view for of the Tasks journal is created in the on_page_loaded event handler:
function on_page_loaded(task) {
$("#title").html(task.item_caption);
if (task.safe_mode) {
$("#user-info").text(task.user_info.role_name + ' ' + task.user_info.user_name);
$('#log-out')
.show()
.click(function(e) {
e.preventDefault();
task.logout();
});
}
task.init_tabs($("#content"));
task.tasks.view($("#content"));
$(window).on('resize', function() {
resize(task);
});
}
on_after_append(item)
domain: client
language: javascript
class Item class
Occurs after an application inserts or appends a record.
The item
parameter is an item that triggered the event.
Write an on_after_append event handler to take specific action immediately after an application inserts or appends a record in an item. on_after_append is called by insert or append method.
on_after_apply(item)
domain: client
language: javascript
class Item class
Occurs after an application saves the change log to the project database.
The item
parameter is an item that triggered the event.
Write an on_after_apply
event handler to take specific action immediately after
an application saves data changes to the project database.
On_after_apply is triggered by apply method.
on_after_cancel(item)
domain: client
language: javascript
class Item class
Occurs after an application cancels modifications made to the item dataset.
The item
parameter is an item that triggered the event.
Write an on_after_cancel event handler to take specific action immediately after an application cancels modifications made to the item dataset.
on_after_delete(item)
domain: client
language: javascript
class Item class
Occurs after an application deletes a record.
The item
parameter is an item that triggered the event.
Write an on_after_delete event handler to take specific action immediately after an application deletes the active record in an item. on_after_delete is called by delete after it deletes the record, and repositions the cursor on the record prior to the one just deleted.
on_after_edit(item)
domain: client
language: javascript
class Item class
Occurs after an application starts editing a record.
The item
parameter is an item that triggered the event.
Write an on_after_delete event handler to take specific action immediately after an application starts editing a record. on_after_edit is called by edit.
on_after_open(item)
domain: client
language: javascript
class Item class
Occurs after an application receives a response from the server for obtaining a dataset.
The item
parameter is an item that triggered the event.
Write an on_after_open
event handler to take specific action immediately
after an application obtains an dataset from the server. on_after_open
is
called by open method.
on_after_post(item)
domain: client
language: javascript
class Item class
Occurs after an application posts a record to the item dataset.
The item
parameter is an item that triggered the event.
Write an on_after_post event handler to take specific action immediately after an application posts a record in the item dataset. on_after_post is called by post method.
on_after_scroll(item)
domain: client
language: javascript
class Item class
Occurs after an application scrolls from one record to another.
The item
parameter is an item that triggered the event.
Write an on_after_scroll event handler to take specific action immediately after an application scrolls to another record as a result of a call to the first, last, next, prior, and locate methods. on_after_scroll is called after all other events triggered by these methods and any other methods that switch from record to record in the item dataset.
The following code is used in the Demo project to asynchronously open invoice_table detail dataset after the Invoice journal record has changed:
var ScrollTimeOut;
function on_after_scroll(item) {
clearTimeout(ScrollTimeOut);
ScrollTimeOut = setTimeout(
function() {
item.invoice_table.open(function() {});
},
100
);
}
on_before_append(item)
domain: client
language: javascript
class Item class
Occurs before an application inserts or appends a record.
The item
parameter is an item that triggered the event.
Write an on_before_append event handler to take specific action immediately before an application inserts or appends a record in an item. on_before_append is called by insert or append method.
on_before_apply(item, params)
domain: client
language: javascript
class Item class
Occurs before an application saves dataset changes to the project database.
The item
parameter is an item that triggered the event.
The params
parameter is an object that has been passed to the
apply
method or an empty object if this object is undefined.
This object is passed to the server and can be used in the
on_apply
event handler to perform some actions when saving changes to the database.
Write an on_before_apply event handler to take specific action immediately before an application saves the change log to the project database.
on_before_apply
is triggered by
apply
method.
on_before_cancel(item)
domain: client
language: javascript
class Item class
Occurs before an application cancels modifications made to the item dataset.
The item
parameter is an item that triggered the event.
Write an on_before_cancel event handler to take specific action immediately before an application cancels modifications made to the item dataset.
on_before_delete(item)
domain: client
language: javascript
class Item class
Occurs before an application deletes a record.
The item
parameter is an item that triggered the event.
Write an on_before_delete event handler to take specific action immediately before an application deletes the active record in an item. on_before_delete is called by delete method before it deletes the record.
on_before_edit(item)
domain: client
language: javascript
class Item class
Occurs before an application enables editing of the active record.
The item
parameter an the item that triggered the event.
Write an on_before_edit event handler to take specific action immediately before an application enables editing of the active record in an item dataset. on_before_edit is called by edit method.
on_before_field_changed(field)
domain: client
language: javascript
class Item class
Write an on_before_field_changed
event handler to implement any special
processing before field’s data has been changed.
The field
parameter is the field whose data is about to be changed. To get the
item that owns the field, use the
owner
attribute of the field.
Before triggering this event handler the application assigns the new value that is
about to be set to the new_value
attribute to of the field. You can change
the value of this attribute. This value will be used to change field’s data.
function on_before_field_changed(field) {
if (field.field_name === 'quantity' && field.new_value < 0) {
field.new_value = 0;
}
}
on_before_open(item, params)
domain: client
language: javascript
class Item class
Occurs before an application sends a request to the server for obtaining a dataset.
The item
parameter is an item that triggered the event.
The params
parameter is an object that has been passed to the
open
method or an empty object if this object is undefined.
This object is passed to the server and can be used in the
on_open
event handler to perform some actions when obtaining a dataset
Write an on_before_open event handler to take specific action immediately before an application obtains an dataset from the server.
on_before_open is called by open method.
on_before_post(item)
domain: client
language: javascript
class Item class
Occurs before an application posts a record to the item dataset.
The item
parameter is an item that triggered the event.
Write an on_before_post event handler to take specific action immediately before an application posts a record in the item dataset. on_before_post is called by post method.
on_before_scroll(item)
domain: client
language: javascript
class Item class
Occurs before an application scrolls from one record to another.
The item
parameter is an item that triggered the event.
Write an on_before_scroll event handler to take specific action immediately before an application scrolls to another record as a result of a call to the first, last, next, prior, and locate methods. on_before_scroll is called before all other events triggered by these methods and any other methods that switch from record to record in the item dataset.
on_detail_changed(item, detail)
domain: client
language: javascript
class Item class
Occurs after changes to detail record has been posted. It uses the clearTimeout and setTimeout Javascript functions so if records have been changed in a cicle it is triggered only when last record change occurs.
The item
parameter is an item that triggered the event.
The detail
parameter is a detail that has been changed.
Write an on_detail_changed event handler to calculate, by using calc_summary method, sums for fields of a detail and save these values in fields of its master.
function on_detail_changed(item, detail) {
var fields;
if (detail.item_name === 'invoice_table') {
fields = [
{"total": "total"},
{"tax": "tax"},
{"subtotal": "amount"}
];
item.calc_summary(detail, fields);
}
}
on_edit_form_close_query(item)
domain: client
language: javascript
class Item class
The on_edit_form_close_query
event is triggered by the
close_edit_form
method of the item.
The item
parameter is the item that triggered the event.
on_edit_form_created(item)
domain: client
language: javascript
class Item class
The on_edit_form_created event is triggered by the create_edit_form method when the form has been created but not shown yet.
The item
parameter is the item that triggered the event.
on_edit_form_keydown(item, event)
domain: client
language: javascript
class Item class
on_edit_form_keyup(item, event)
domain: client
language: javascript
class Item class
on_edit_form_shown(item)
domain: client
language: javascript
class Item class
The on_edit_form_shown event is triggered by the create_edit_form method when the form has been shown.
The item
parameter is the item that triggered the event.
on_field_changed(field, lookup_item)
domain: client
language: javascript
class Item class
Write an on_field_changed
event handler to respond to any changes in the
field’s data.
The field
parameter is the field whose data has been changed. To get the
item that owns the field, use the
owner
attribute of the field.
The lookup_item
parameter is not undefined
when
the field is a
lookup field
and a change has occured
when a user selected a record from a lookup item dataset.
function on_field_changed(field, lookup_item) {
var item = field.owner;
if (field.field_name === 'quantity' || field.field_name === 'unitprice') {
item.owner.calc_total(item);
}
else if (field.field_name === 'track' && lookup_item) {
item.quantity.value = 1;
item.unitprice.value = lookup_item.unitprice.value;
}
}
on_field_get_html(field)
domain: client
language: javascript
class Item class
Write an on_field_get_html
event handler to specify the html that will be
inserted in the table cell for the field.
If the event handler does not return a value, the application checks if the on_field_get_text event handler is defined and it returns a value, otherwise the display_text property value will be used to display the field value in the cell.
The field
parameter is the field whose
display_text
is processed. To get the item that owns the field, use the
owner attribute of the field.
function on_field_get_html(field) {
if (field.field_name === 'total') {
if (field.value > 10) {
return '<strong>' + field.display_text + '</strong>';
}
}
}
on_field_select_value(field, lookup_item)
domain: client
language: javascript
class Item class
When user clicks on the button to the right of the field input or uses typeahead,
the application creates a copy of the lookup item of the field and
triggers on_field_select_value
event. Use on_field_select_value
to
specify fields that will be displayed, set up filters for the lookup item, before
it will be opened.
The field
parameter is the field whose data will be selected.
The lookup_item
parameter is a copy of the lookup item of the field
function on_field_select_value(field, lookup_item) {
if (field.field_name === 'customer') {
lookup_item.set_where({lastname__startwith: 'B'});
lookup_item.view_options.fields = ['firstname', 'lastname', 'address', 'phone'];
}
}
on_field_validate(field)
domain: client
language: javascript
class Item class
Write an on_field_validate
event handler to validate changes made to the
field data.
The field
parameter is the field whose data has been changed. To get the
item that owns the field, use the
owner
attribute of the field.
The event handler must return a string if the field value is invalid. When an event handler returns a string, the application throws an exception.
The event is triggered when the post method is called or when the user leaves the input used to edit the field value.
function on_field_validate(field) {
if (field.field_name === 'sum' && field.value > 10000000) {
return 'The sum is too big.';
}
}
on_filter_changed(filter, lookup_item)
domain: client
language: javascript
class Item class
on_filter_form_close_query(item)
domain: client
language: javascript
The on_filter_form_close_query
event is triggered by the
close_filter_form
method of the item.
The item
parameter is the item that triggered the event.
on_filter_form_created(item)
The item parameter is the item that triggered the event.
domain: client
language: javascript
The on_filter_form_created event is triggered by the create_filter_form method when the form has been created but not shown yet.
The item
parameter is the item that triggered the event.
on_filter_form_shown(item)
The item parameter is the item that triggered the event.
domain: client
language: javascript
The on_filter_form_shown event is triggered by the create_filter_form method when the form has been shown.
The item
parameter is the item that triggered the event.
on_filter_record(item)
The item parameter is the item that triggered the event.
domain: client
language: javascript
Use an on_filter_record
event to filter dataset records locally.
It is triggered when the cursor moves to another record and
Filtered
property is set to true
Write an on_filter_record
event handler to specify for each record in a
dataset whether it should be visible to the application. To indicate that a
record passes the filter condition, the on_filter_record
event handler must
return true.
The item
parameter is the item that triggered the event.
function on_filter_record(item) {
if (item.type.value === 2) {
return true;
}
}
function enable_filtering(item) {
item.filtered = true;
}
function disable_filtering(item) {
item.filtered = false;
}
on_filters_applied(item)
domain: client
language: javascript
class Item class
Write an on_filters_applied
event handler to make special processing when
filters have been applied to the item dataset.
on_field_get_text(field)
domain: client
language: javascript
class Item class
Write an on_field_get_text
event handler to perform custom processing for the
display_text
property. If the event handler does not return a value, the application uses
the
display_text
property value to display the field value in the data-aware controls, otherwise
the returned value will be used.
The field
parameter is the field whose
display_text
is processed. To get the item that owns the field, use the
owner attribute of the field.
function on_field_get_text(field) {
if (field.field_name === 'customer') {
return field.owner.firstname.lookup_text + ' ' + field.lookup_text;
}
}
on_view_form_close_query(item)
domain: client
language: javascript
class Item class
The on_view_form_close_query
event is triggered by the
close_view_form
method of the item.
The item
parameter is the item that triggered the event.
on_view_form_created(item)
domain: client
language: javascript
class Item class
on_view_form_keydown(item, event)
domain: client
language: javascript
class Item class
on_view_form_keyup(item, event)
domain: client
language: javascript
class Item class
on_view_form_shown(item)
domain: client
language: javascript
class Item class
Detail
()¶domain: client
language: javascript
Detail class inherits attributes, methods and events of Item class
Reports
()¶domain: client
language: javascript
Reports class is used to create the group object of the task tree that owns the reports of a project.
Below the events of the class are listed.
It, as well, inherits attributes and methods of its ancestor class AbstractItem class
on_before_print_report(item)
domain: client
language: javascript
class Reports class
The on_before_print_report
event is triggered by the
process_report
method.
The report
parameter is the report that triggered the event.
on_open_report(report)
domain: client
language: javascript
class Reports class
The on_open_report
event is triggered by the
process_report
method.
The report
parameter is the report that triggered the event.
on_param_form_close_query(item)
domain: client
language: javascript
class Reports class
The on_param_form_close_query
event is triggered by the
close_param_form
method.
The report
parameter is the report that triggered the event.
on_param_form_created(item)
domain: client
language: javascript
class Reports class
The on_param_form_created
event is triggered by the
create_param_form
method, that, usually, is called by then
print
method.
The report
parameter is the report that triggered the event.
on_param_form_shown(item)
domain: client
language: javascript
class Reports class
The on_param_form_shown
event is triggered by the
create_param_form
method, that, usually, is called by then
print
method.
Report
()¶domain: client
language: javascript
Report class inherits
Below the attributes, methods and events of the class are listed.
It, as well, inherits attributes and methods of its ancestor class AbstractItem class
extension
domain: client
language: javascript
class Report class
Use extension
attribute to specify a report type. The server, based on the
report template, first generates ods file. And if report extention is other
that ods performs convertion using the LibreOffice.
The attribute value can be any extension that LibreOffice supports convertion to.
function on_before_print_report(report) {
report.extension = 'html';
}
param_form
¶domain: client
language: javascript
class Report class
Use param_form
attribute to get access to a Jquery object representing the
param form of the report.
It is created by the create_param_form method, that, usually, is called by then print method.
The
close_param_form
method sets the param_form
value to undefined.
function on_param_form_created(report) {
report.create_param_inputs(report.param_form.find(".edit-body"));
report.param_form.find("#cancel-btn").on('click.task', function() {
report.close_param_form();
});
report.param_form.find("#ok-btn").on('click.task', function() {
report.process_report()
});
}
param_options
¶domain: client
language: javascript
class Report class
Use the param_options
attribute to specify parameters of the modal param form.
param_options
is an object that has the following attributes:
width
- the width of the modal form, the default value is 560 px,title
- the title of the modal form, the default value is the value of a
report_caption
attribute,close_button
- if true, the close button will be created in the upper-right
corner of the form, the default value is true,close_caption
- if true and close_button is true, will display ‘Close - [Esc]’
near the buttonclose_on_escape
- if true, pressing on the Escape key will trigger the
close_param_form
method.close_focusout
- if true, the
close_param_form
method will be called when a form loses focustemplate_class
- if specified, the div with this class will be searched in
the task
templates
attribute and used as a form html template when creating a formfunction on_param_form_created(report) {
report.param_options.width = 800;
report.param_options.close_button = false;
report.param_options.close_on_escape = false;
}
close_param_form
()¶domain: client
language: javascript
class Report class
Use close_param_form
method to close the param form of the report.
The close_param_form
method triggers the
on_param_form_close_query
event handler of the report, if one is defined. If the event handler is defined
and
If it don’t return a value (undefined) the method triggers the on_param_form_close_query of the group that owners the report, if one is defined for the group. If this event handler is defined and
If it don’t return a value (undefined) the method triggers the on_param_form_close_query of the task. If this event handler is defined and
If no event handler is defined or none of these event handlers return false, the form is destroyed and the report’s param_form atrribute is set to undefined.
create_param_form
()¶domain: client
language: javascript
class Report class
The create_param_form
method is called by the
print
method to create a form to set report parameters before sending a request to
the server by the
process_report
method.
The method checks if javascript modules of the report and its owner are loaded, and if not (the Dynamic JS modules loading parameter is set) then loads them.
Then it searches for the report html template in the task templates attribute (See Forms ) and creates a clone of the template and assigns it to the report param_form attribute.
Creates a form and appends the html to it.
Triggers the on_param_form_created of the task.
Triggers the on_param_form_created of the report group, if one is defined.
Triggers the on_param_form_created of the report, if one is defined.
Shows the form. Before showing the form the method applies options specidied in the param_options attribute.
Triggers the on_param_form_shown of the task.
Triggers the on_param_form_created of the report group, if one is defined.
Triggers the on_param_form_shown of the report, if one is defined.
create_param_inputs
(container, options)¶domain: client
language: javascript
Use create_param_inputs
to create data-aware visual controls (inputs,
cheboxes) for editing of report parameters.
This method is usually used in on_param_form_created
events triggered by
create_param_form
method, that, usually, is called by then
print
method.
The following parameters are passed to the method:
container
- a JQuery object that will contain visual controls, if
container length is 0 (no container), the method returns.options
- options that specify how controls are displayedThe options
parameter is an object that may have following attributes:
params
- a list of param names. If specified, a visual control will be
created for each param whose name is in this list, if not specified
(the default) then control will be created for all visible params specified in the
Application buildercol_count
- the number of columns that will be created for visual controls,
the default value is 1label_on_top
: the default value is false. If this value is false, the
labels are placed to the left of controls, otherwise the are created above the
controlstabindex
- if tabindex is specified, it will the tabindex of the first
visual control, tabindex of all subsequent controls will be increased by 1.autocomplete
- the default value is false. If this attribute is set to
true, the autocomplete attribute of controls is set to “on”Before creating controls the application empties the container.
function on_param_form_created(item) {
item.create_param_inputs(item.param_form.find(".edit-body"));
item.param_form.find("#cancel-btn").on('click.task', function() {
item.close_param_form()
});
item.param_form.find("#ok-btn").on('click.task', function() {
item.process_report()
});
}
print
(create_form)domain: client
language: javascript
class Report class
Use print
to print the report.
If create_form
parameter is omitted or equals true, the method calls the
create_param_form
method to create a form based on the html template defined in the index.html
file.
If create_form
parameter is set to false
and the report has no visible
parameters, it calls
process_report to send request to server to generate
the report, otherwise it calls
create_param_form
method.
process_report
()¶domain: client
language: javascript
class Report class
The process_report method sends the report to the server to generate its content and accepts the report file that the server returns to the client and opens or saves it.
It is called by the
print
method direclly, if its create_form
parameter equals false and there are no
visible parameters. If there are visible parameters, the
print method creates a form to specify parameter values and the
form should call it (for example, by some button onclick event ).
The checks if parameter values are valid and the triggers the following events:
In this event handlers developer can define some common (report group event handler) or specific (report event handler) attributes of the report.
After that the process_report method sends asynchronous request to the server to generate a report content. (see Server-side report programming ).
The server returns to the method an url to a file with the generated report content.
The method then checks if the on_open_report event handler of the report group is defined. If this events handler if defined calls it, otherwise checks the on_open_report of the report. If it is defined then calls it.
If none of this events are defined, it (depending on the report extension attribute) opens the report in the browser or saves it to disc.
In the following event handler, defined in the client module of the invoice report of the Demo application, the value of the report id parameter is set:
function on_before_print_report(report) {
report.id.value = report.task.invoices.id.value;
}
on_before_print_report(report)
domain: client
language: javascript
class Report class
The on_before_print_report
event is triggered by the
process_report
method. Use on_before_print_report
to take specific actions before sending
request to the server to generate the report.
The report
parameter is the report that triggered the event.
on_open_report(report)
domain: client
language: javascript
class Report class
The on_open_report
event is triggered by the
process_report
method.
The report
parameter is the report that triggered the event.
on_param_form_close_query(report)
domain: client
language: javascript
class Report class
The on_param_form_close_query
event is triggered by the
close_param_form
method.
The report
parameter is the report that triggered the event.
on_param_form_created(report)
domain: client
language: javascript
class Report class
The on_param_form_created
event is triggered by the
create_param_form
method, that, usually, is called by then
print
method.
The report
parameter is the report that triggered the event.
on_param_form_shown(report)
domain: client
language: javascript
class Report class
The on_param_form_shown
event is triggered by the
create_param_form
method, that, usually, is called by then
print
method.
The report
parameter is the report that triggered the event.
Field
()¶domain: client
language: javascript
display_text
¶domain: client
language: javascript
class Field class
Represents the field’s value as a string.
Display_text property is a read-only string representation of a field’s value to display in a data-aware control. If an on_get_field_text event handler is assigned, display_text is the value returned by this event handler. Otherwise, display_text is the value of the lookup_text property for lookup fields , and text property, converted according to the language locale settings, for other fields.
Display_text is the string representation of the field’s value property when it is not being edited. When the field is being edited, the text property is used.
function on_get_field_text(field) {
if (field.field_name === 'customer') {
return field.owner.firstname.lookup_text + ' ' + field.lookup_text;
}
}
field_caption
¶domain: client
language: javascript
class Field class
Field_caption attribute specifies the name of the field that appears to users.
field_mask
¶domain: client
language: javascript
class Field class
You can use field_mask attribute to specify the name of the field that appears to
The mask allows a user to more easily enter fixed width input where you would like them to enter the data in a certain format (dates,phone numbers, etc).
A mask is defined by a format made up of mask literals and mask definitions. Any character not in the definitions list below is considered a mask literal. Mask literals will be automatically entered for the user as they type and will not be able to be removed by the user.The following mask definitions are predefined:
function on_edit_form_created(item) {
item.phone.field_mask = '999-99-99';
}
field_name
¶domain: client
language: javascript
class Field class
Specifies the name of the field as referenced in code. Use field_name to refer to the field in code.
field_size
¶domain: client
language: javascript
class Field class
Identifies the size of the text field object.
field_type
¶domain: client
language: javascript
class Field class
Identifies the data type of the field object.
Use the field_type attribute to learn the type of the data the field contains. It is one of the following values:
lookup_text
¶domain: client
language: javascript
class Field class
Use lookup_text property to get the lookup value of the lookup field converted to string.
If the field is lookup field gives its lookup text, otherwise gives the value of the text property
lookup_type
¶domain: client
language: javascript
class Field class
For lookup fields identifies the type of the lookup_value, otherwise returns the value of field_type attribute.
lookup_value
¶domain: client
language: javascript
class Field class
Use lookup_value property to get the lookup value of the lookup field
If the field is lookup field gives its lookup value, otherwise gives the value of the value property
owner
domain: client
language: javascript
class Field class
Identifies the item to which a field object belongs.
Check the value of the owner attribute to determine the item that uses the field object to represent one of its fields.
function calculate(item) {
}
function on_field_changed(field, lookup_item) {
if (field.field_name === 'taxrate') {
calculate(field.owner);
}
}
raw_value
¶domain: client
language: javascript
class Field class
read_only
¶domain: client
language: javascript
class Field class
Determines whether the field can be modified in data-aware controls.
Set read_only to true to prevent a field from being modified in data-aware controls.
required
domain: client
language: javascript
class Field class
Specifies whether a not empty value for a field is required.
Use required to find out if a field requires a value or if the field can be blank. When required property is set to true, trying to post a null value will cause an exception to be raised.
text
domain: client
language: javascript
class Field class
Use text property to get or set the text value of the field.
value
domain: client
language: javascript
class Field class
Use value property to get or set the value of the field.
When field data is null, the field converts it to 0, if the field_type is “integer”, “float” or “currency”, or to empty string if field_type is “text”.
For lookup fields the value of this property is an integer that is the value of the id field of the corresponding record in the lookup item. To get lookup value of the field use the lookup_value property.
When a new value is assigned, the field checks if the current value is not equal to the new one. If so it
function calc_total(item) {
item.amount.value = item.round(item.quantity.value * item.unitprice.value, 2);
item.tax.value = item.round(item.amount.value * item.owner.taxrate.value / 100, 2);
item.total.value = item.amount.value + item.tax.value;
}
download
()domain: client
language: javascript
class Field class
Call download
for fields of type FILE to download the file.
function on_view_form_created(item) {
item.add_view_button('Download').click(function() {
item.attachment.download();
});
}
open
()domain: client
language: javascript
class Field class
Call open
for fields of type FILE to open the url to the file by using
window.open
.
function on_view_form_created(item) {
item.add_view_button('Open').click(function() {
item.attachment.open();
});
}
Filter
()¶domain: client
language: javascript
filter_caption
¶domain: client
language: javascript
class Filter class
Filter_caption attribute specifies the name of the filter that appears to users.
filter_name
¶domain: client
language: javascript
class Filter class
Specifies the name of the filter as referenced in code. Use filter_name to refer to the field in code.
owner
domain: client
language: javascript
Identifies the item to which a filter object belongs.
Check the value of the owner attribute to determine the item that uses the filter object to represent one of its filters.
value
domain: client
language: javascript
class Filter class
Use value property to get or set the value of the filter.
function on_view_form_created(item) {
item.filters.invoicedate1.value = new Date(new Date().setYear(new Date().getFullYear() - 1));
}
visible
domain: client
language: javascript
class Filter class
If the value of this property is true the input control for this filter will be created by the create_filter_inputs method, if the filters option in not specidied.
All objects of the framework represent a task tree. Bellow is classes for each kind of task tree objects:
App
¶domain: server
language: python
App class is used to create a WSGI application
Below the attributes of the class are listed.
AbstractItem
¶domain: server
language: python
AbstractItem class is the ancestor for all item objects of the task tree
Below the attributes and methods of the class are listed.
environ
domain: server
language: python
class AbstractItem class
Specifies the WSGI environment dictionary of the current request from the client.
ID
¶domain: server
language: python
class AbstractItem class
The ID
attribute is the unique in the framework id of the item
The ID
attribute is most useful when referring to the item by number rather than
name. It is also used internally.
item_caption
¶domain: server
language: python
class AbstractItem class
Specifies the name of the item that appears to users
item_name
¶domain: server
language: python
class AbstractItem class
Specifies the name of the item as referenced in code.
Use item_name
to refer to the item in code.
item_type
¶domain: server
language: python
class: AbstractItem class
Specifies the type of the item.
Use the item_type
attribute to get the type of the item. It can have one of the
following values
items
domain: server
language: python
class AbstractItem class
Lists all items owned by the item.
Use items
to access any of the item owned by this object.
Indicates the item that owns this item.
owner
domain: server
language: python
class AbstractItem class
Use owner
to find the owner of an item.
session
domain: server
language: python
class AbstractItem class
Use the session
property to get access to session object of the current
request from the client.
The session is a dictionary that has the following items:
ip
- ip address of the useruser_info
- dictionary containing information about the useruser_id
- id identifying the useruser_name
- name of the userrole_id
- id of user rolerole_name
- name of user roledef on_open(item, params):
user_id = item.session['user_info']['user_id']
if user_id:
params['__filters'].append(['user_id', item.task.consts.FILTER_EQ, user_id])
def on_apply(item, delta, params):
user_id = item.session['user_info']['user_id']
if user_id:
for d in delta:
d.edit()
d.user_id.value = user_id
d.post()
can_view
(self)¶domain: server
language: python
class AbstractItem class
Use the can_view
method to determine whether a user of the current session
can view records if a data item or print a report.
Task
¶domain: server
language: python
Task class is used to create the root of the Task tree of the project.
Below the attributes, methods and events of the class are listed.
It, as well, inherits attributes and methods of its ancestor class AbstractItem class
app
domain: server
language: python
class: Task class
Returns a reference to WSGI application object.
The Framework uses Werkzeug WSGI Utility Library.
check_password_hash
(self, pwhash, password)¶domain: server
language: python
class Task class
Use check_password_hash
to check a password against a given salted and hashed
password value.
The method is wrapper over Werkzeug check_password_hash function: https://werkzeug.palletsprojects.com/en/0.15.x/utils/
def on_login(task, login, password, ip, session_uuid):
users = task.users.copy(handlers=False)
users.set_where(login=login)
users.open()
for u in users:
if task.check_password_hash(u.password_hash.value, password):
return {
'user_id': users.id.value,
'user_name': users.name.value,
'role_id': users.role.value,
'role_name': users.role.display_text
}
connect
(self)domain: server
language: python
class Task class
Use connect
to procure a connection from the SQLAlchemy connection pool.
The return value of this method is a DBAPI connection.
A developer must return a connection to the connection poll when it is no
longer needed by calling close
method of the connection.
def delete_rec(item, item_id):
conection = item.task.connect()
try:
cursor = conection.cursor()
cursor.execute('delete from %s where id=%s' % (item.table_name, item_id))
conection.commit()
finally:
conection.close()
copy_database
(self, dbtype, database=None, user=None, password=None, host=None, port=None, encoding=None, server=None)¶domain: server
language: python
class Task class
Use copy_database
to copy database data when migrating to another database.
in the following code when the project task tree is created the application copies the data from the demo.sqlite database to the project database:
from jam.db.db_modules import SQLITE
def on_created(task):
task.copy_database(SQLITE, '/home/work/demo/demo.sqlite')
create_connection
(self)¶domain: server
language: python
class Task class
Use create_connection
to create a connection to the project database.
The method returns a new connection.
A developer must close a connection after it is no longer needed.
create_connection_ex
(self, db_module, database, user=None, password=None, host=None, port=None, encoding=None, server=None)¶domain: server
language: python
class Task class
Use create_connection_ex
to create a connection to other databases.
The method returns a new connection.
A developer must close a connection after it is no longer needed.
execute
(self, sql)domain: server
language: python
class Task class
Use execute
to execute an SQL query (except SELECT queries) using
multiprocessing connection pool. For SELECT queries use the
select
method.
The sql
parameter can be a query string, a list of query strings, a list of
lists and so on.
All queries are executed in one transaction and if execution succeeds the COMMIT
command is called, otherwise ROLLBACK
command is executed.
sql = []
for i in ids:
sql.append('UPDATE DEMO_CUSTOMERS SET QUANTITY=2 WHERE ID=%s' % i)
item.task.execute(sql)
generate_password_hash
(self, password, method='pbkdf2:sha256', salt_length=8)¶domain: server
language: python
class Task class
This method hash a password with the given method and salt with a string of the given length. The format of the string returned includes the method that was used so that check_password_hash can check the hash.
The method is wrapper over Werkzeug generate_password_hash function: https://werkzeug.palletsprojects.com/en/0.15.x/utils/
def on_apply(item, delta, params, connection):
for d in delta:
if d.password.value:
d.edit();
d.password_hash.value = delta.task.generate_password_hash(d.password.value)
d.password.value = None
d.post();
lock
(self, lock_name, timeout=-1)domain: server
language: python
class Task class
Use lock
to implement a platform independent file lock in Python, which
provides a simple way of inter-process communication.
This method is a wrapper around Python filelock library: https://github.com/benediktschmitt/py-filelock
Once lock has been acquired, subsequent attempts to acquire it block execution, until it is released.
lock_name
parameter is the name of the lock. It must be unic in the
application. The filelock library creates a file in the locks folder with
this name and .lock extention that it uses to implement the lock.
timeout
parameter - if the lock cannot be acquired within timeout seconds, a
Timeout exception is raised.
The code
def calculate(item):
lock = item.task.lock('calculation'):
lock.acquire()
try:
#some code
finally:
lock.release()
is equivalent to
def calculate(item):
with item.task.lock('calculation'):
#some code
The example with timeout:
from jam.third_party.filelock import Timeout
def calculate(item):
try
with item.task.lock('calculation', timeout=10):
#some code
except Timeout:
print("Another instance of this application currently holds the lock.")
In the following example when saving invoice the app calculates sold tracks. Before doing this it acquires a lock:
def on_apply(item, delta, params):
with item.task.lock('invoice_saved'):
tracks_sql = []
delta.update_deleted()
for d in delta:
for t in d.invoice_table:
if t.rec_inserted():
sql = "UPDATE DEMO_TRACKS SET TRACKS_SOLD = COALESCE(TRACKS_SOLD, 0) + \
%s WHERE ID = %s" % \
(t.quantity.value, t.track.value)
elif t.rec_deleted():
sql = "UPDATE DEMO_TRACKS SET TRACKS_SOLD = COALESCE(TRACKS_SOLD, 0) - \
(SELECT QUANTITY FROM DEMO_INVOICE_TABLE WHERE ID=%s) WHERE ID = %s" % \
(t.id.value, t.track.value)
elif t.rec_modified():
sql = "UPDATE DEMO_TRACKS SET TRACKS_SOLD = COALESCE(TRACKS_SOLD, 0) - \
(SELECT QUANTITY FROM DEMO_INVOICE_TABLE WHERE ID=%s) + %s WHERE ID = %s" % \
(t.id.value, t.quantity.value, t.track.value)
tracks_sql.append(sql)
sql = delta.apply_sql()
return item.task.execute(tracks_sql + [sql])
select
(self, sql)domain: server
language: python
class Task class
Use select
to execute select SELECT
SQL query. To execute the query the
connection pool is used.
The sql
parameter is a query to execute.
The method returns a list of records.
recs = item.task.execute_select("SELECT * FROM DEMO_CUSTOMERS WHERE ID=41")
for r in rec:
print(r)
on_created(task)
domain: server
language: python
class Task class
Use on_created
to initialize the application on the server side.
The event is triggered when the project task tree has just been created. See Workflow
The task
parameter is a reference to the
task tree
Note
The execution time of the code in this handler must be very short because of detrimental effects to the end user’s experience.
def on_created(task):
# some code
on_ext_request(task, request, params)
domain: server
language: python
class Task class
Use on_ext_request
to send a request to the server for processing.
The task
parameter is a reference to the
task tree
The request
is a string that must starts with ‘/ext’
There could be a list of parameters.
The following application will send every 60 seconds a request to the server of Demo application
#!/usr/bin/env python
try:
# For Python 3.0 and later
from urllib.request import urlopen
except ImportError:
# Fall back to Python 2's urllib2
from urllib2 import urlopen
import json
import time
def send(url, request, params):
a = urlopen(url + '/' + request, data=str.encode(json.dumps(params)))
r = json.loads(a.read().decode())
return r['result']['data']
if __name__ == '__main__':
url = 'http://127.0.0.1:8080/ext'
while True:
result = send(url, 'get_sum', [1, 2, 3])
print(result)
time.sleep(60)
The server will process this request and return the sum of parameters. The
on_ext_request
must be declared in task server module:
def on_ext_request(task, request, params):
#print request, params
reqs = request.split('/')
if reqs[2] == 'get_sum':
return params[0] + params[1] + params[2]
on_login(task, form_data, info)
domain: server
language: python
class Task class
Use on_login
to override default login procedure using Application
Builder Users table.
task
parameter is a reference to the
task tree.
form_data
is a dictionary containing the values that the user entered in
the inputs in the login form. The keys of the dictionary are name attributes
of the inputs.
info
parameter is a dictionary with the following attributes:
ip
is the ip address of the requestsession_uuid
is uuid of the session that will be created.The event handler must return the dictionary with the following attributes:
user_id
- the unique id of the useruser_name
- user namerole_id
- ID of the role defined in the
Rolesrole_name
- role nameThe login form is located in the index.html file. You can add your own custom inputs and get their values using form_data parameter
<form id="login-form" target="dummy" class="form-horizontal" data-caption="Log in">
<div class="control-group">
<label class="control-label" for="input-login">Login</label>
<div class="controls">
<input type="text" id="input-login" name="login" tabindex="1" placeholder="login">
</div>
</div>
<div class="control-group">
<label class="control-label" for="input-password">Password</label>
<div class="controls">
<input type="password" id="input-password" name="password" tabindex="2"
placeholder="password" autocomplete="on">
</div>
</div>
<div class="form-footer">
<input type="submit" class="btn expanded-btn pull-right" id="login-btn" value="OK" tabindex="3">
</div>
</form>
In this example user information is stored in the table of the Users item in the project database:
def on_login(task, form_data, info):
users = task.users.copy(handlers=False)
users.set_where(login=form_data['login'])
users.open()
if users.rec_count == 1:
if task.check_password_hash(users.password_hash.value, form_data['password']):
return {
'user_id': users.id.value,
'user_name': users.name.value,
'role_id': users.role.value,
'role_name': users.role.display_text
}
Group
¶domain: server
language: python
Group class is used to create group objects of the task tree
It, as well, inherits attributes and methods of its ancestor class AbstractItem class
Item
¶domain: server
language: python
Item class is used to create item objects of the task tree that may have an associated database table.
Below the attributes, methods and events of the class are listed.
It, as well, inherits attributes and methods of its ancestor class AbstractItem class
active
domain: server
language: python
class Item class
Specifies whether or not an item dataset is open.
Use active
read only property to determine whether an item dataset is open.
The
open
method changes the value of active
to true
. The
close method sets it to false
.
When the dataset is open its records can be navigated and its data can be modified and the changes saved in the item database table.
fields
domain: server
language: python
class Item class
def customer_fields(customers):
customers.open(limit=1)
for f in customers.fields:
print f.field_caption, f.display_text
filters
domain: server
language: python
class Item class
def invoices_filters(invoices):
for f in invoices.filters:
print f.filter_name, f.value
item_state
¶domain: server
language: python
class Item class
Examine item_state
to determine the current operating mode of the item.
Item_state determines what can be done with data in an item dataset, such as
editing existing records or inserting new ones. The item_state
constantly
changes as an application processes data.
Opening a item changes state from inactive to browse. An application can call edit to put an item into edit state, or call insert or append to put an item into insert state.
Posting or canceling edits, insertions, or deletions, changes item_state
from its current state to browse. Closing a dataset changes its state to
inactive.
To check item_state value use the following methods:
item_state value can be:
item task attribute have consts object that defines following attributes:
so if the item is in edit state can be checked the following way:
item.item_state == 2
or:
item.item_state == item.task.consts.STATE_INSERT
or:
item.is_new()
log_changes
¶domain: server
language: python
class Item class
Indicates whether to log data changes.
Use log_changes
to control whether or not changes made to the data in an
item dataset are recorded. When log_changes
is true
(the default), all
changes are recorded. They can later be applied to an application server by
calling the
apply
method. When log_changes
is false
, data changes are not recorded and cannot
be applied to an application server.
rec_no
¶domain: server
language: python
class Item class
Examine the rec_no
property to determine the record number of the current
record in the item dataset.
rec_no
can be set to a specific record number to position the cursor on that
record.
table_name
¶domain: server
language: python
class Item class
Read this property to get the name of the corresponding table in the project database.
virtual_table
¶domain: server
language: python
class Item class
Use the read-only virtual_table
property to find out if the item has a
corresponding table in the project database.
If virtual_table
is True
there is no corresponding table in the project
database. You can use these items to work with in-memory dataset or use its
modules to write code.
Calling the
open
method creates an empty data set, and calling the
apply
method does nothing.
append
(self)domain: server
language: python
class Item class
Open a new, empty record at the end of the dataset.
After a call to append
, an application can enable users to enter data in the
fields of the record, and can then post those changes to the item dataset using
post
method, and then apply them to the item database table, using
apply
method.
The append
method
apply(self, connection=None, params=None, safe=False):
domain: server
language: python
class Item class
Writes all updated, inserted, and deleted records from the item dataset to the database.
The apply
method
on_before_apply
event handler if one is defined for the itemconnection
parameter is None
the task
connect method is called to get a connection
from task connection poolon_apply
event handler of the task is defined, executes itconnection
parameter was not specified, commits changes to the database and
returns connection to the connection poolon_after_apply
event handler if one is defined for the itemconnection
- if this parameter is specified the appication uses it to
execute sql query that it generates (it doesn’t commit changes and doesn’t
close the connection), otherwise it procures a connection from the task connection
pool that will be returned to the pool after changes are commited.params
- use the parameter to pass some user defined options to be used in
the
on_apply
event handler. This parameter must be an object of key-value pairssafe
- if set to True
, the method checks if the user that called the
method has a right to create, edit or delete records in the item’s database
table (if such operation is going to be performed) and, if not, raises
an exception. The default value is False
.
See
RolesIn the second example below, the changes are saved in one transaction.
def change_invoice_date(item, item_id):
inv = item.copy()
cust = item.task.customers.copy()
inv.set_where(id=item_id)
inv.open()
if inv.record_count():
now = datetime.datetime.now()
cust.set_where(id=inv.customer.value)
cust.open()
inv.edit()
inv.invoice_datetime.value = now
inv.post()
inv.apply()
cust.edit()
cust.last_action_date.value = now
cust.post()
cust.apply()
def change_invoice_date(item, item_id):
con = item.task.connect()
try:
inv = item.copy()
cust = item.task.customers.copy()
inv.set_where(id=item_id)
inv.open()
if inv.record_count():
now = datetime.datetime.now()
cust.set_where(id=inv.customer.value)
cust.open()
inv.edit()
inv.invoice_datetime.value = now
inv.post()
inv.apply(con)
cust.edit()
cust.last_action_date.value = now
cust.post()
cust.apply(con)
finally:
con.commit()
con.close()
bof
(self)domain: server
language: python
class Item class
Test bof
(beginning of file) method to determine if the cursor is positioned
at the first record in an item dataset.
If bof returns true, the cursor is unequivocally on the first row in the dataset. bof returns true when an application
bof returns false in all other cases.
Note
If both eof and bof return true, the item dataset is empty.
can_create
(self)¶domain: server
language: python
class AbstractItem class
Use the can_create
method to determine whether a user of the current session
have a right to create a new record.
def send_email(item, selected, subject, mess):
if not item.can_create():
raise Exception('You are not allowed to send emails.')
#code sending email
can_delete
(self)¶domain: server
language: python
class AbstractItem class
Use the can_delete
method to determine whether a user of the current session
have a right to delete a record.
can_edit
(self)¶domain: server
language: python
class AbstractItem class
Use the can_edit
method to determine whether a user of the current session
have a right to edit a record.
cancel
(self)domain: server
language: python
class Item class
Call cancel
to undo modifications made to one or more fields belonging to
the current record, as long as those changes are not already posted to the item
dataset.
Cancel
on_before_cancel
event handler if one is defined for the item.on_after_cancel
event handler if one is defined for the item.clear_filters
(self)¶domain: server
language: python
class Item class
Use clear_filters
to set filter values of the item to None
.
close
(self)domain: server
language: python
class Item class
copy
(self, filters=True, details=True, handlers=True)domain: server
language: python
class Item class
Use copy to create a copy of an item. The created copy is not added to the task tree and will be destroyed by Python garbage collector when no longer needed.
All attributes of the copy object are defined as they were at the time of creating of the task tree. See Workflow
The method can have the following parameters:
handlers
- if the value of this parameter is true
, all the functions and
events defined in the server module of the item will also be available in the
copy. The default value is true
.filters
- if the value of this parameter is true
, the filters will be
created for the copy, otherwise there will be no filters. The default value
is true
.details
- if the value of this parameter is true
, the details will be
created for the copy, otherwise there will be no details. The default value
is true
.def on_generate(report):
cust = report.task.customers.copy()
cust.open()
report.print_band('title')
for c in cust:
firstname = c.firstname.display_text
lastname = c.lastname.display_text
company = c.company.display_text
country = c.country.display_text
address = c.address.display_text
phone = c.phone.display_text
email = c.email.display_text
report.print_band('detail', locals())
delete
(self)domain: server
language: python
class Item class
Deletes the active record and positions the cursor on the next record.
The delete
method
edit
(self)domain: server
language: python
class Item class
Enables editing of data in the dataset.
After a call to edit
, an application can enable users to change data in the
fields of the record, and can then post those changes to the item dataset using
post method, and then apply them to database using
apply method.
The edit
method
eof
(self)domain: server
language: python
class Item class
Test eof
(end-of-file) to determine if the cursor is positioned at the last
record in an item dataset. If eof returns true, the cursor is unequivocally on
the last row in the dataset. eof returns true when an application:
eof returns false in all other cases.
Note
If both eof and bof return true, the item dataset is empty.
field_by_name
(self, field_name)¶domain: server
language: python
class Item class
Call field_by_name
to retrieve field information for a field when only its
name is known.
The field_name
parameter is the name of an existing field.
field_by_name
returns the field object for the specified field. If the
specified field does not exist, field_by_name
returns None
.
filter_by_name
(self, filter_name)¶domain: server
language: python
class Item class
Call filter_by_name
to retrieve filter information for a filter when only its
name is known.
The filter_name
parameter is the name of an existing filter.
filter_by_name
returns the filter object for the specified filter. If the
specified filter does not exist, filter_by_name
returns None
.
first
(self)domain: server
language: python
class Item class
Call first
to position the cursor on the first record in the item dataset and
make it the active record. First
posts any changes to the active record.
insert
(self)domain: server
language: python
class Item class
Inserts a new, empty record in the item dataset.
After a call to insert
, an application can enable users to enter data in the
fields of the record, and can then post those changes to the item dataset using
post
method, and then apply them to the item database table, using
apply
method.
The insert
method
is_changing
(self)¶domain: server
language: python
class Item class
Checks if an item is in edit or insert state and returns true if it is.
An application calls edit to put an item into edit state and append or insert to put an item into insert state.
is_edited
(self)¶domain: server
language: python
class Item class
Checks if an item is in edit state and returns true if it is.
An application calls edit to put an item into edit state.
is_modified
(self)¶domain: server
language: python
class Item class
Checks if the current record of an item dataset has been modified during edit or
insert opertaions. The method returns false
after the
post method is executed.
is_new
(self)¶domain: server
language: python
class Item class
Checks if an item is in insert state and returns true if it is.
An application calls append or insert methods to put an item into insert state.
last
(self)domain: server
language: python
class Item class
Call last
to position the cursor on the last record in the item dataset and
make it the active record.
locate
(self, fields, values)domain: server
language: python
class Item class
Implements a method for searching an item dataset for a specified record and makes that record the active record.
Arguments:
fields
: a field name, or list of field namesvalues
: a field value of list of field valuesThis method locates the record where the fields specified by fields
parameter
have the values specified by values
parameter.
locate
returns true if a record is found that matches the specified criteria and
the cursor repositioned to that record.
If a matching record was not found and the cursor is not repositioned, this method returns false.
next
(self)domain: server
language: python
class Item class
Call next
to position the cursor on the next record in the item dataset and
make it the active record. Next posts any changes to the active record.
open(self, options=None, expanded=None, fields=None, where=None,
order_by=None, open_empty=False, params=None, offset=None, limit=None,
funcs=None, group_by=None, safe=False)
domain: server
language: python
class Item class
Call open
to generate and execute a SELECT SQL query to the item database
table for obtaining a dataset.
The method initializes the item
fields,
formulates parameters of a request, and triggers the
on_before_open
event handler if one is defined for the item.
If there is a
on_open
event handler defined for the item, open
executes this event handler and
assigns a dataset to the result, returned by it,
otherwise generates a SELECT SQL query, based on parameters of the request,
executes this query and assigns the result of the execution to the dataset
After that it sets
active
to true, the
item_state
to browse mode, goes to the first record of the dataset, triggers
on_after_open
, if it is defined for the item.
You can pass options
dictionary to specify parameters of the request in the same
form as for the
open
method on the client:
invoices.open({
'fields': ['customer', 'invoicedate', 'total'],
'where': {customer: customer_id, invoicedate__ge: date1, invoicedate__le: date2},
'order_by': ['invoicedate']
})
or pass the keyworded arguments:
invoices.open(
fields=['customer', 'invoicedate', 'total'],
where={customer: customer_id, invoicedate__ge: date1, invoicedate__le: date2},
order_by=['invoicedate']
)
expanded
- if the value of this parameter is true, the SELECT query will
have JOIN clauses to get lookup values of the
lookup fields
, otherwise there will be no lookup values. The default value if true
.fields
- use this parameter to specify the WHERE clause of the SELECT
query. This parameter is a list of field names. If it is omitted, the fields
defined by the
set_fields
method will be used. If the
set_fields
method was not called before the open
method execution, all available fields
will be used.where
- use this parameter to specify how records will be filtered in the
SQL query. This parameter is a dictionary, whose keys are
field names, that are followed, after double underscore, by a filtering symbols
(see
Filtering records
). If this parameter is omitted, values
defined by the
set_where
method will be used. If the
set_where
method was not called before the open
method execution, and where
parameter is omitted, then the values of
filters
defined for the item will be used to filter records.order_by
- use order_by
to specify sort order of the records. This
parameter is a list of field names. If there is a sign ‘-’ before the field
name, then on this field records will be sorted in decreasing order. If this
parameter is omitted, a list defined by the
set_order_by
method will be used.offset
- use offset
to specify the offset of the first row to get.limit
- use limit
to limit the output of a SQL query to the first
so-many rows.funcs
- this parameter can be a a dictionary, whose keys are
a field names and values are function names that will be applied to the fields
in the SELECT Querygroup_by
- use group_by
to specify fields to group the result of the
query by. This parameter must be a list of field names.open_empty
- if this parameter is set to true
, the application does
not send a request to the server but just initializes an empty dataset.
The default value if false
.params
- use the parameter to pass some user defined options to be used in
the
on_open
event handler. This parameter must be an object of key-value pairssafe
- if set to True
the method checks if the user that called the
method has a right to view the item’s data and, if not, raises an exception.
The default value is False
.
See
RolesIn this example the parameters of the request are a dictionary:
import datetime
def get_sales(item):
date1 = datetime.datetime.now() - datetime.timedelta(days=3*365)
date2 = datetime.datetime.now()
invoices = item.task.invoices.copy()
invoices.open({
'fields': ['customer', 'date', 'total'],
'where': {'date__ge': date1, 'date__le': date2},
'order_by': ['customer', 'date']
})
Below the parameters are passed as a keyworded list:
import datetime
def get_sales(item):
date1 = datetime.datetime.now() - datetime.timedelta(days=3*365)
date2 = datetime.datetime.now()
invoices = item.task.invoices.copy()
invoices.open(
fields=['customer', 'date', 'total'],
where={'date__ge': date1, 'date__le': date2},
order_by=['customer', 'date']
)
The same result can be achieved by using set_fields, set_where, set_order_by methods:
import datetime
def get_sales(item):
date1 = datetime.datetime.now() - datetime.timedelta(days=3*365)
date2 = datetime.datetime.now()
invoices = item.task.invoices.copy()
invoices.set_fields('customer', 'date', 'total')
invoices.set_where(date__ge=date1, date__le=date2);
invoices.set_order_by('customer', 'date');
invoices.open();
import datetime
date1 = datetime.datetime.now() - datetime.timedelta(days=3*365) date2 = datetime.datetime.now() invoices = item.task.invoices.copy()
invoices.set_fields([‘customer’, ‘date’, ‘total’]) invoices.set_where({‘date__ge’: date1, ‘date__le’: date2}); invoices.set_order_by([‘customer’, ‘date’]); invoices.open();
def get_sales(task) {
sales = task.invoices.copy()
sales.open(fields=['customer', 'id', 'total'],
funcs={'id': 'count', 'total': 'sum'},
group_by=['customer'],
order_by=['customer'])
post
(self)domain: server
language: python
class Item class
Writes a modified record to the item dataset. Call post to save changes made to a record after append, insert or edit method was called.
The post
method
on_before_post
event handler if one is defined for the itemon_after_post
event handler if one is defined for the item.prior
(self)domain: server
language: python
class Item class
Call prior
to position the cursor on the previous record in the item dataset and
make it the active record. last posts any changes to the active record.
record_count
()¶domain: server
language: python
class Item class
Call record_count
to get the total number of records ownered by the item’s
dataset.
item.open()
if item.record_count():
# some code
set_fields
(self, lst=None, *fields)¶domain: server
language: python
class Item class
Use the set_fields
method to define and store internally the fields
parameter that will be used by the
open
method, when its own fields
parameter is not specified. The
open
method clears internally stored parameter value.
The fields
is arbitrary argument list of field names.
You can specify the fields as a list, the way the set_fields method on the client does or as non-keyworded arguments.
The result of the execution of following code snippets wil be the same:
item.open(fields=['id', 'invoicedate'])
item.set_fields('id', 'invoicedate')
item.open()
item.set_fields(['id', 'invoicedate'])
item.open()
set_order_by
(self, lst=None, *fields)¶domain: server
language: python
class Item class
Use the set_order_by
method to define and store internally the order_by
parameter that will be used by the
open
method, when its own order_by
parameter is not specified. The
open
method clears internally stored parameter value.
You can specify the fields as a list, the way the set_order_by method on the client does or as non-keyworded arguments.
If there is a sign ‘-’ before a field name, then on this field records will be sorted in decreasing order.
The result of the execution of following code snippets will be the same:
item.open(order_by=['customer', '-invoicedate'])
item.set_order_by('customer', '-invoicedate')
item.open();
item.set_order_by(['customer', '-invoicedate'])
item.open();
set_where
(self, dic=None, **fields)¶domain: server
language: python
class Item class
Use the set_where
method to define and store internally the where filters
that will be used by the
open
method, when its own where parameter is not specified. The
open
method clears internally stored parameter value.
You can specify the filters as a dictionary, the way the set_where method on the client does or as keyworded arguments
The result of the execution of following code snippets wil be the same:
import datetime
date = datetime.datetime.now() - datetime.timedelta(days=3*365)
item.open(where={'customer': 44, 'invoicedate__gt': date})
import datetime
date = datetime.datetime.now() - datetime.timedelta(days=3*365)
item.set_where({'customer': 44, 'invoicedate__gt': date})
item.open()
import datetime
date = datetime.datetime.now() - datetime.timedelta(days=3*365)
item.set_where(customer=44, invoicedate__gt=date)
item.open()
on_apply(self, delta, params, connection)
domain: server
language: python
class Item class
Write on_apply
event handler when you need to override the standard data saving
procedure during the execution of the apply method on the
client
or
server.
See on_apply events to understand how on_apply events are triggered.
The on_apply
event handler has the following parameters:
item
- a reference to the item,delta
- a delta containing item change log (discussed in more detail below),params
- the parameters passed to the server by apply method,connection
- the connection that will be used to save changes to the database.The delta parameter contains changes that must be saved in the database. By itself, this option is an item’s copy, and its dataset is the item’s change log. The nature of the record change can be obtained by using following methods:
rec_inserted
rec_modified
rec_deleted
each of which returns a value of True
, if the record is added, modified or
deleted, respectively.
If the item has a detail items, delta also has a corresponding detail items, storing detail changes.
Note
Please note that when a record is deleted from an item and this record has
detail records, the change log will just keep this deleted record,
information about the deleted records of the details is not stored.
To add this deleted detail records, call delta’s update_deleted
method.
You do not need to open delta detail after the cursor has been moved to another record.
Delta dataset fields have an old_value attribute that can be used to get the value of a field before changes have been made.
Fields of the delta dataset have an old_value
attribute that can be used to
get the value of a field before changes have been made.
when the on_apply
event handler is not defined the apply_delta
method
is executed, that generates SQL queries and executes them. After that it
returns the information about the result of processing, that stores the
id’s of the new records as well. The client based on this information updates the
item’s change log and values of the primary fields of new records.
When on_apply
event handler returns None
the apply_delta
is executed.
You can make some additional processing of the delta. In the following code, the a value of the date field is set to the current date before changes are applied to the database table.
import datetime
def on_apply(item, delta, params, connection):
for d in delta:
d.edit()
d.date.value = datetime.datetime.now()
d.post()
Note
Please note that changes made this way are not reflected in the item dataset on the client. You can use the item client methods refresh_record or refresh_page to display these changes.
In the following code, while saving the changes made to the invoices, the
application as well updates the value of the tracks_sold
field for tracks in
this invoices. All this is done in one transaction.
def on_apply(item, delta, params, connection):
tracks = item.task.tracks.copy()
changes = {}
delta.update_deleted()
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())
tracks.set_where(id__in=ids)
tracks.open()
for t in tracks:
q = changes.get(t.id.value)
if q:
t.edit()
t.tracks_sold.value += q
t.post()
tracks.apply(connection)
In the previous examples the on_apply
event handler returns None
so after
that the apply_delta
method is executed by the application.
The more general case is:
def on_apply(item, delta, params, connection):
# execute some code before changes are written to the database
result = item.apply_delta(delta, params, connection)
# execute some code after changes are written to the database
return result
on_open(item, params)
domain: server
language: python
class Item class
Write on_open
event handler when you need to override the standard
procedure of fetching the records from the dataset during the execution of the
open method on the
client
or
server.
See on_open_events to understand how on_open events are triggered.
The on_open
event handler has the following parameters:
item
- reference to the item,
params
- dictionary containing parameters passed to the server by the open
method:
__expanded
- corresponds to the expanded
parameter of the server
open method / expanded
attribute of options parameter of
the client
open
method__fields
- list of field names__filters
- list of items, each of which is a list with the following members:__funcs
- functions dictionary__order
- list of items, each of which is a list with the following members:__offset
- corresponds to the offset parameter of the open method__limit
- corresponds to the limit parameter of the open method__client_request
- is true when request came from the clientparams
can also include user defined parameters passed to the open method.
Below is an example of params that the client open methods of invoices items sends to the server:
{
'__fields': [u'id', u'deleted', u'customer', u'firstname', u'date',
u'subtotal', u'taxrate', u'tax', u'total',
u'billing_address', u'billing_city', u'billing_country',
u'billing_postal_code', u'billing_state'],
'__filters': [[u'customer', 7, [6]]],
'__expanded': True,
'__limit': 11,
'__offset': 0,
'__order': [[u'date', True]]
}
{
'__fields': [u'id'],
'__funcs': {u'id': u'count'},
'__filters': [],
'__expanded': False,
'__offset': 0,
'__order': [],
'__summary': True
}
The server application generates an SQL query, based on params and executes them.
The server returns to the client the resulting records and the error message if it occurs during the execution.
Detail
¶domain: server
language: python
Detail class inherits attributes, methods and events of Item class
Reports
¶domain: server
language: python
Reports class is used to create the group object of the task tree that owns the reports of a project.
Below the events of the class are listed.
It, as well, inherits attributes and methods of its ancestor class AbstractItem class
on_convert_report
(report)¶domain: client
language: python
class: Reports class
The framework converts reports internally, using LibreOffice. Use the on_convert_report event if you want to use some other service or change some parameters of report conversion.
The report parameter is the report that triggered the event.
import os
from subprocess import Popen, STDOUT, PIPE
def on_convert_report(report):
try:
if os.name == "nt":
import _winreg
regpath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\soffice.exe"
root = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, regpath)
s_office = _winreg.QueryValue(root, "")
else:
s_office = "soffice"
convertion = Popen([s_office, '--headless', '--convert-to', report.ext,
report.report_filename, '--outdir', os.path.join(report.task.work_dir, 'static', 'reports') ],
stderr=STDOUT,stdout=PIPE)
out, err = convertion.communicate()
converted = True
except Exception as e:
print(e)
Report
¶domain: server
language: python
Report class inherits
Below the attributes, methods and events of the class are listed.
It, as well, inherits attributes and methods of its ancestor class AbstractItem class
report_filename
¶domain: client
language: python
class Report class
When the template attribute of the report is specified, the generate method saves the content of the generated report to a file in a report folder of the static directory and set the value of the report_filename attribute to the name of the saved file.
Its value can be used in the on_after_generate event handler.
report_url
¶domain: client
language: python
class Report class
template
domain: client
language: python
class Report class
The report_filename attribute stores a file name of the report template. Usually it is set in the Application builder when the report is created. But it can be changed dynamically on the server in the on_before_generate event handler or be empty, if it’s necessary to create, for exapmle, some txt file.
generate
(self)domain: client
language: python
class Report class
The method is used internally to generate the content of the report.
When a server gets a request from a client to generate report, it first of all creates a copy of the report and then this copy calls the method.
This method triggers the on_before_generate event.
If the report template is defined, parses it and triggers on_parsed and on_generate events.
In the on_generate event handler developer should write a code that generates the content of the report and saves it in the ods file, by using the print_band method to print bands .
When the report is generated and the value of report extension attribute, set on the client, is not equal ods, the server tries to convert the ods file.
To convert the file, it first checks if the report group (owner of the report) has on_convert_report event handler. If this handler is defined it uses it to convert the report. Otherwise it uses for conversion the LibreOffice installed on the server in headless mode.
After that the application saves generated report to a file in a report folder of the static directory, set the value of the report_filename attribute to the name of the saved file, generates the value of the report_url attribute and triggers on_after_generate event.
Once the report is generated it is saved in a report folder of the static directory and the server sends the client the report file url.
If the report template attribute is not set, the server triggers the on_generate and after that on_after_generate events. In this case you should save the generated content to a file yourself and and set a value of the report_url attribute.
hide_columns
(self, col_list)¶domain: client
language: python
class Report class
Use hide_columns method to hide some columns defined in the report template.
The col_list parameter specifies which columns should be hidden. This is a list of integers or letters, defining the position of the report columns.
Use this method in the on_parsed event handler.
def on_parsed(report):
report.hide_columns(['A', 'C')
# report.hide_columns([1, 3])
print_band
(self, band, dic=None)¶domain: client
language: python
class Report class
Use print_band method to set values of programmable cells of the band defined in the report template and add the band to the content of the report.
It has the following parameters:
The following code generates content of the Customer list report of the Demo application:
def on_generate(report):
cust = report.task.customers.copy()
cust.open()
report.print_band('title')
for c in cust:
firstname = c.firstname.display_text
lastname = c.lastname.display_text
company = c.company.display_text
country = c.country.display_text
address = c.address.display_text
phone = c.phone.display_text
email = c.email.display_text
report.print_band('detail', locals())
on_after_generate
(report)¶domain: client
language: python
class Report class
The on_after_generate event is triggered by the generate method, when the report has been generated and saved to a file with the name that is stored in the report_filename attribute.
The report parameter is the report that triggered the event.
on_before_generate
(report)¶domain: client
language: python
class Report class
on_generate
(report)¶domain: client
language: python
class Report class
The on_generate event is triggered by the generate method.
Write the on_generate event handler to generate the content of the report. Use the print_band method to print bands, defined in the report template.
The report parameter is the report that triggered the event.
on_parsed
(report)¶domain: client
language: python
class Report class
The on_parsed event is triggered by the generate method, after the report template have been parsed.
Use this event handler you hide some columns in the report template by calling hide_columns
The report parameter is the report that triggered the event.
Field
¶domain: server
language: python
display_text
¶domain: server
language: python
Represents the field’s value as a string.
Display_text
property is a read-only string representation of a field’s value
to display it to users. If an
on_get_field_text
event handler is assigned, display_text is the value returned by this event
handler. Otherwise, display_text is the value of the
lookup_text
property for
lookup fields
and
text
property converted according to the
language locale
settings
for other fields.
Display_text
is the string representation of the field’s value property when
it is not being edited. When the field is being edited, the
text
property is used.
def on_generate(report):
cust = report.task.customers.copy()
cust.open()
report.print_band('title')
for c in cust:
firstname = c.firstname.display_text
lastname = c.lastname.display_text
company = c.company.display_text
country = c.country.display_text
address = c.address.display_text
phone = c.phone.display_text
email = c.email.display_text
report.print_band('detail', locals())
field_caption
¶domain: server
language: python
Field_caption
attribute specifies the name of the field that appears to
users.
field_name
¶domain: server
language: python
Specifies the name of the field as referenced in code.
Use field_name
to refer to the field in code.
field_size
¶domain: server
language: python
Identifies the size of the text field object.
field_type
¶domain: server
language: python
Identifies the data type of the field object.
Use the field_type
attribute to learn the type of the data the field contains.
It is one of the following values:
lookup_text
¶domain: server
language: python
Use lookup_text
property to get the lookup value of the
lookup field converted to string.
If the field is lookup field gives its lookup text, otherwise gives the value of the text property
lookup_value
¶domain: server
language: python
Use lookup_value
property to get the lookup value of the
lookup field
If the field is lookup field gives its lookup value, otherwise gives the value of the value property
owner
domain: server
language: python
Identifies the item to which a field object belongs.
Check the value of the owner attribute to determine the item that uses the field object to represent one of its fields.
raw_value
¶domain: server
language: python
read_only
¶domain: server
language: python
Determines whether the field can be modified in data-aware controls.
Set read_only
to true
to prevent a field from being modified in
data-aware controls.
required
domain: server
language: python
Specifies whether a not empty value for a field is required.
Use required
to find out if a field requires a value or if the field can be
blank. When required
property is set to true, trying to post a null value
will cause an exception to be raised.
text
domain: server
language: python
Use text
property to get or set the text value of the field.
value
domain: server
language: python
Use value
property to get or set the value of the field.
When field data is null
, the field converts it to 0
, if
the field_type is “integer”, “float” or “currency”, or to empty string if
field_type is “text”.
For lookup fields the value of this property is an integer that is the value of the id field of the corresponding record in the lookup item. To get lookup value of the field use the lookup_value property.
When a new value is assigned, the field checks if the current value is not equal to the new one. If so it
new_value
attribute to this value,new_value
attribute and sets it to null
,true
Filter
¶domain: server
language: python
filter_name
¶domain: server
language: python
class Filter class
Specifies the name of the filter as referenced in code.
Use filter_name
to refer to the field in code.
owner
domain: server
language: python
class Filter class
Identifies the item to which a filter object belongs.
Check the value of the owner attribute to determine the item that uses the filter object to represent one of its filters.
value
domain: server
language: python
class Filter class
Use value
property to get or set the value of the filter.
function on_view_form_created(item) {
item.filters.invoicedate1.value = new Date(new Date().setYear(new Date().getFullYear() - 1));
}
Version 1 was designed to develop database desktop applications based on the GTK Toolkit.
In version 2, support for developing database applications with a web interface was added.
In version 3, support for development of database desktop applications based on the GTK Toolkit was removed.
In Version 4 the server side was reworked. Web.py library was replaced with werkzeug. Session support was added.
Jam.py library:
Demo application:
Twitter account created: https://twitter.com/jampy_framework
Jam.py Users Mailing List created: https://groups.google.com/forum/#!forum/jam-py
Jam library:
Demo application:
Jam.py:
Administrator:
Demo application:
Jam.py:
Admin:
Demo:
Jam.py:
Demo application:
Documentation:
Jam.py:
Administrator:
Demo application:
Library:
Demo:
Library:
Demo:
Library:
Default font is 14px now you can change it to 12px font by replacing
<link href="jam/css/jam.css" rel="stylesheet">
with
<link href="jam/css/jam12.css" rel="stylesheet">
in index.html
Administrator is renamed to Application builder you can run it by typing 127.0.0.1:8080/builder.html, 127.0.0.1:8080/admin.html is also supported
Asterisk is added to required fields now
To cancel it add
.control-label.required:after {
content: "";
}
to project.css file
Selection of lookup list value in report parameters for fixed.
Documentation:
First version of Documentation completed
New topics added:
Jam.py roadmap added
Demo:
Library:
Application Builder:
Library:
Demo project:
Library:
A set of client methods of the task for working with tabs developed
Forms are reworked. Each form now have a div with modal-header class declared in the index.html file. The elements for search input and filter text are removed from the form templates and placed in the form header.
The view, append_record, insert_record and edit_record methods are reworked. If a container parameter is passed to these methods and the init_tabs method is called for the conainer, the tabs are created that contains the forms.
For existing projects add the line
task.init_tabs($("#content"));
at the beginning on the on_page_loaded event handler of the task client module to forms be desplayed in tabs and add a $(“#content”) container parameter to append_record, insert_record and edit_record methods.
You can add a line
task.add_form_borders = false;
if you don’t want to change html templates of the forms. Otherwise remove elements for search input and filter text (in the div with form-header class, remove it) from the form templates and add the div with modal-header class to templates.
Demo:
Documentation:
Library:
Safe mode bug (after version 5.3.1) fixed
Postgres import bug fixed
Task attribute edit_form_container
is defined in the on_page_loaded event
handler of the task client module of a new project and demo application
task.edit_form_container = $("#content"); // comment this line to have modal edit forms
Note
If you created your project with a version of the library less than 4.3.1 add the following line in the on_page_loaded event handler in the task’s client module:
task.old_forms = true;
For libraries with versions 4.3, clear the code of client modules of catalogs and journals and replace client module of the task with the corresponding code of the Demo application or the new project. Make an archive of the project before doing it.
Library:
Application builder:
Note
To use masks in existing projects the following line must be added to index.html after package update:
<script src=”jam/js/jquery.maskedinput.js”></script>
before
<script src=”jam/js/jam.js”></script>
Library:
Library:
Application Builder:
Library:
Library:
Demo application:
Library:
Application builder:
Library:
Library:
Demo application:
Library:
Documetation:
Library:
Application Builder:
Demo application:
Library:
Documentation:
Library:
Documentation:
deployment section added to How to
How to lock a record so that users cannot edit it at the same time topic added
Library:
Documentation:
Library:
Documentation:
Library:
show_hints
and hint_fields
attributes can be added to
the table_options or options
parameter of the create_table method.Documentation:
Demo application
Library:
To use record locking for items for which you defined on_apply event handler you must change. Add the connection parameter, create a cursor and use the cursor to execute sql queries. Otherwise the record locking won’t work.
For example, the code
def on_apply(item, delta, params):
tracks_sql = []
delta.update_deleted()
for d in delta:
for t in d.invoice_table:
if t.rec_inserted():
sql = "UPDATE DEMO_TRACKS SET TRACKS_SOLD = COALESCE(TRACKS_SOLD, 0) + \
%s WHERE ID = %s" % \
(t.quantity.value, t.track.value)
elif t.rec_deleted():
sql = "UPDATE DEMO_TRACKS SET TRACKS_SOLD = COALESCE(TRACKS_SOLD, 0) - \
(SELECT QUANTITY FROM DEMO_INVOICE_TABLE WHERE ID=%s) WHERE ID = %s" % \
(t.id.value, t.track.value)
elif t.rec_modified():
sql = "UPDATE DEMO_TRACKS SET TRACKS_SOLD = COALESCE(TRACKS_SOLD, 0) - \
(SELECT QUANTITY FROM DEMO_INVOICE_TABLE WHERE ID=%s) + %s WHERE ID = %s" % \
(t.id.value, t.quantity.value, t.track.value)
tracks_sql.append(sql)
sql = delta.apply_sql()
return item.task.execute(tracks_sql + [sql])
must be changed to
def on_apply(item, delta, params, connection):
with item.task.lock('invoice_saved'):
cursor = connection.cursor()
delta.update_deleted()
for d in delta:
for t in d.invoice_table:
if t.rec_inserted():
sql = "UPDATE DEMO_TRACKS SET TRACKS_SOLD = COALESCE(TRACKS_SOLD, 0) + \
%s WHERE ID = %s" % \
(t.quantity.value, t.track.value)
elif t.rec_deleted():
sql = "UPDATE DEMO_TRACKS SET TRACKS_SOLD = COALESCE(TRACKS_SOLD, 0) - \
(SELECT QUANTITY FROM DEMO_INVOICE_TABLE WHERE ID=%s) WHERE ID = %s" % \
(t.id.value, t.track.value)
elif t.rec_modified():
sql = "UPDATE DEMO_TRACKS SET TRACKS_SOLD = COALESCE(TRACKS_SOLD, 0) - \
(SELECT QUANTITY FROM DEMO_INVOICE_TABLE WHERE ID=%s) + %s WHERE ID = %s" % \
(t.id.value, t.quantity.value, t.track.value)
cursor.execute(sql)
Library:
Library:
Application builder:
Documetation:
expanded
options is added to the
add_edit_button and
add_view_button
methods.We plan to add the following features to Jam.py: