=============================
Jam.py documentation contents
=============================
.. toctree::
:hidden:
index
.. toctree::
:maxdepth: 3
intro/index
programming/index
faq/index
how_to/index
admin/index
refs/index
releases/index
jampy-design-doco/contents
=======================
Jam.py V7 documentation
=======================
Introduction
==============
Welcome to Jam.py! If you are new to Jam.py or no-code, low-code or more-code Web application development, this is the place to find documentation about the Jam.py V7.
The biggest difference to Jam.py V5 is :doc:`routing ` support and Bootstrap 5, enabling modern support for mobile devices (see :doc:`version 7 `).
In addition, the complete interface for :doc:`Desktop ` or :doc:`Mobile ` devices is driven by a no-code approach.
Just select what is needed for any device and off you go.
.. admonition:: Objectives
Installing Python and Jam.py, choosing the database and the Web server and making Application Design decisions.
.. admonition:: Audience
Web development enthusiasts or developers, with a limited or no experience with the Web development, or deployment.
.. admonition:: Prerequisites
Some Python and JavaScript knowledge is recommended. The general knowledge about the Command Line prompt, and typing
the commands is required.
Feed the LLM
==============
The LLMS-full.txt is released:
https://jampy-docs-v7.readthedocs.io/en/latest/llms-full.txt
and:
https://jampy-docs-v7.readthedocs.io/en/latest/llms.txt
The Github Jam.py repository:
https://github.com/jam-py-v5/jam-py-v7
Documentation Layout Overview
==================================
Here is an overview of the documentation layout, to
help you find out where to look for specific details:
:doc:`Getting started ` topics describe how to install the framework, create
a new project, develop a web application step-by-step, and deploy it.
:doc:`Programming guides`
discuss key topics and concepts at a fairly high level and provide useful
background information and explanation.
:doc:`Business application builder ` is a detailed description of
the Application Builder used for application development and database
administration.
:doc:`Class reference guides ` contain technical reference for
Jam.py classes APIs.
:doc:`FAQ ` topics covers most frequently asked questions.
:doc:`How to ` contains code examples that can be useful to quickly
accomplish common tasks.
Please visit the
:doc:`table of contents ` or even
`Jam.py Application Design Tips`_, for detailed steps how to build the applications or migrate from MS Access.
.. _`Jam.py Application Design Tips`: https://jampy-application-design-tips.readthedocs.io/
To download this document as a single PDF, please visit:
https://jampy-docs-v7.readthedocs.io/_/downloads/en/latest/pdf/
Video Tutorials
===============
If you are new to Jam.py, we highly recommend that you watch these video tutorials. The videos are referring to
Jam.py V5. There are minimal changes to the User interface compared to V7.
It is recommended to watch these videos with a resolution of 1080p.
Tutorial 1 - `Working with files and images`_
.. _`Working with files and images`: https://youtu.be/9rFXPyfN0Hg
Tutorial 2 - `Working with details`_
.. _`Working with details`: https://youtu.be/sbvxE-vEfsM
Tutorial 3 - `Users, roles, audit trail/change history`_
.. _`Users, roles, audit trail/change history`: https://youtu.be/60LiWZa0CpY
Tutorial 4 - `Task tree`_
.. _`Task tree`: https://youtu.be/hsSKqEh6vL4
Tutorial 5 - Forms_
.. _Forms: https://youtu.be/3sh-TSt52P0
Tutorial 6 - `Form events`_
.. _`Form events`: https://youtu.be/DY463lcv0R4
Tutorial 7 - `Data aware controls`_
.. _`Data aware controls`: https://youtu.be/fMTq8P4XdGw
Tutorial 8 - `Datasets`_
.. _`Datasets`: https://youtu.be/gHTYj7h9ljI
Tutorial 9 - `Datasets Part 2`_
.. _`Datasets Part 2`: https://youtu.be/1bUGmgBfrNw
Tutorial 10 - `Fields and filters`_
.. _`Fields and filters`: https://youtu.be/ahXqlZrA0fQ
Tutorial 11 - `Client-server interactions`_
.. _`Client-server interactions`: https://youtu.be/nLOhdA2FX0I
Tutorial 12 - `Working with data on the server`_
.. _`Working with data on the server`: https://youtu.be/dDK78lIjHHY
===============
Getting started
===============
Here you can learn how to install the framework, create a new project,
develop a web application and deploy it.
.. toctree::
:maxdepth: 1
install
new_project
demo_project
tutorial01/index
tutorial02/index
tutorial03/index
deployment
admin
============
Installation
============
Install python
==============
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 3:
* Python 3.4 and newer
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.
>>>
For Python 3 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.
>>>
Install Jam.py
====================
Installing an official release with `pip`
-----------------------------------------
This is the recommended way to install Jam.py.
1. 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.)
2. If you're using Linux, Mac OS X or some other flavour of Unix, enter the
command:
.. code-block:: console
sudo pip install jam.py-v7
at the shell prompt.
If you're using Windows, start a command shell with administrator privileges
and run the command::
...\> python -m pip install jam.py-v7
This will install Jam.py in your Python installation's ``site-packages`` directory.
.. _pip: http://www.pip-installer.org/
.. _standalone pip installer: http://www.pip-installer.org/en/latest/installing.html#install-pip
Installing an official release manually
---------------------------------------
1. Download the package archive.
2. Create a new directory and unpack the archive there.
3. Go into the directory and run the setup command from command line
.. code-block:: console
$ 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
.. admonition:: Python on Windows
If you are just starting with Jam.py and using Windows, you may find
:doc:`How to install Jam.py on Windows <../how_to/how_to_install_on_windows>` useful.
Understanding admin.sqlite
==========================
The Mind Map for Jam.py V7 admin.sqlite database describes the Jam.py database engine schema. The intention was to quickly find the information needed, as well as the code.
.. raw:: html
:file: ../_static/Jam.py.html
============
Demo project
============
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 downloaded from Github. There is also a standalone or an
portable application for Windows x64 provided `here `_.
The portable application depends on LibreOffice for Reports and nothing more. Just run it and point the browser as per below.
To start the demo application go to the demo folder and run *server.py* script.
.. code-block:: console
$ ./server.py
Open a Web browser and enter
.. code-block:: console
127.0.0.1:8080
in the address bar.
To see the
:doc:`Application builder `,
open a new page in a browser and enter
.. code-block:: console
127.0.0.1:8080/builder.html
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/demo_jampy.png
:scale: 70 %
:align: center
:alt: Demo application
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/demo_admin_jampy.png
:scale: 70 %
:align: center
:alt: Application builder of demo project
===========
Deployment
===========
Jam.py deployment with Apache and mod_wsgi
==========================================
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.
.. code-block:: apache
WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py
WSGIPythonPath /path/to/mysite.com
Require all granted
Alias /static/ /path/to/mysite.com/static/
Require all granted
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 ```` piece just ensures that Apache can access your
:file:`wsgi.py` file.
The next lines ensure that anything in the ``/static/`` URL space is explicitly
served as a static files.
See also
========
See the additional information on the deployment in the
:doc:`How to deploy `
==================
Creating a project
==================
Create a new directory.
Go into the directory and run from command line:
.. code-block:: console
$ jam-project.py
For Windows users, ``jam-project.py`` command is in ``Scripts`` folder, ie.
folder above this one:
.. code-block:: console
...\> ..\project-name\Scripts\jam-project.py
The following files and folders will be created in the directory::
/
css/
js/
reports/
static/
locks/
admin.sqlite
langs.sqlite
server.py
index.html
templates.html
wsgi.py
To start the Jam.py web server, run the ``server.py`` script.
.. code-block:: console
$ ./server.py
For Windows users:
.. code-block:: console
...\>server.py
.. note::
You can specify a port as parameter, for example
.. code-block:: console
$ ./server.py 8081
By default, the port is 8080. If you specify another port, you need to
use it in your browser in the next steps.
You’ll see the similar output on the command line::
User Guide: https://jampy-docs-v7.readthedocs.io/
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:8080
* Running on http://127.0.0.1:8080
Press CTRL+C to quit
If we open a Web browser and visit the app, the below message will display:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/run_builder_jampy.png
:alt: Jam.py new project
Open a Web browser and go to “/builder.html” on your local domain – e.g.:
.. code-block:: console
127.0.0.1:8080/builder.html
You should see the language selection dialog. This defines the language used for the
user interface. You can select the language from the list of default languages, or
import your own, using the "folder" icon to the right of the input field. See the
:doc:`Language support ` page for more information.
Select your language and press the OK button.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/lang_jampy.png
:alt: Jam.py language dialog
Next is the new project dialog. Fill in:
* **Caption** - the project name that will appear to users.
* **Name** - the name of project (task) that will be used in the code (Python or JS)
to get access to the task object. This should be a short and valid python identifier.
This name is also used as a prefix when creating a table in the project database.
* **DB type** - select a database type. If the database is not Sqlite, it must be
created in advance and its attributes should be entered in the
corresponding form fields.
To see examples of Database setup, follow the :doc:`link `.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/project_params_jampy.png
:align: center
:alt: New project setup
When you press OK, the connection to the database will be checked, and in case
of failure an error message will be displayed.
Examples of database setups
===========================
.. admonition:: Adapted from `Jam.py Design Tips`_
Jam.py supports many different database servers. For example PostgreSQL_,
MariaDB_, MySQL_, MSSQL_, Oracle_, Firebird_, IBM_, SQLite_, Databricks_, DuckDB_,
and SQLite with SQLCipher_.
If you are developing a small project or something you don't plan to deploy in
a production environment, SQLite is generally the best option as it doesn't
require running a separate server. However, SQLite has many differences from
other databases, so if you are working on something substantial, it's
recommended to develop with the same database that you plan on using in
production.
In addition to a database backend, we need to make sure the Python
database bindings are installed.
* If using PostgreSQL_, the ``psycopg2`` or ``psycopg2-binary`` package is needed.
* If using MySQL or MariaDB_, the ``MySQLdb`` for Python 2.x is needed. For Python 3.x, the ``mysql-connector-python`` and ``mysqlclient`` package is needed, as well as database client development files.
* If using MSSQL_, the ``pymssql`` is needed.
* If using Oracle_, the `cx_Oracle`_ is needed, as well as Python headers (development files).
* If using SQLCipher_ (TBA), ``sqlcipher3-binary`` package is needed for Linux. There is a standalone DLL for Windows available.
* If using IBM_ (TBA), ``ibm_db`` and ``ibm_db_dbi`` package is needed.
* If using Firebird_, ``fdb`` package is needed.
* If using Databricks_, ``databricks-sql-connector`` is needed.
* If using DuckDB_, ``duckdb`` is needed.
* To generate reports, **LibreOffice** must be installed. :doc:`Portable ` LibreOffice is supported.
.. _Jam.py Design Tips: https://jampy-application-design-tips.readthedocs.io/
.. _PostgreSQL: https://www.postgresql.org/
.. _MariaDB: https://mariadb.org/
.. _MySQL: https://www.mysql.com/
.. _psycopg2: https://www.psycopg.org/
.. _SQLite: https://www.sqlite.org/
.. _cx_Oracle: https://oracle.github.io/python-cx_Oracle/
.. _Oracle: https://www.oracle.com/
.. _MSSQL: https://www.microsoft.com/en-au/sql-server/sql-server-downloads
.. _Firebird: https://firebirdsql.org/
.. _SQLCipher: https://github.com/sqlcipher
.. _IBM: https://www.ibm.com/support/pages/downloading-ibm-db2-version-115-linux-unix-and-windows
.. _Databricks: https://www.databricks.com/blog/how-use-lakebase-transactional-data-layer-databricks-apps
.. _DuckDB: https://github.com/duckdb/duckdb
.. note::
For **SQLite** databases, certain schema changes - such as deleting or renaming a field,
or creating a foreign key - require the
:doc:`Application Builder `
to recreate the table. In this process, a new table is created and all records are
copied from the original table into it.
Additionally, Jam.py does not support importing metadata into an existing SQLite
project (i.e., a project with already created tables).
Metadata can only be imported when creating a new project.
More about the database setups is within the :doc:`Project management `.
If all goes well, a new project will be created and the project tree will appear
in the Application builder.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/adm_new_project_jampy.png
:align: center
:alt: Jam.py Application builder
.. important::
As seen on top right corner, The Name of the application is displayed, name of the database used, application version number, and
``Jam.py`` framework ``version`` number.
Open a new tab, type
.. code-block:: console
127.0.0.1:8080
in the address bar and press Enter.
A new project appears with an empty menu.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/empty_project_jampy.png
:align: center
:alt: Jam.py project
=================
Customizing Forms
=================
When we refresh the project page, we 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.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/contacts_new_jampy.png
:scale: 50%
:align: center
:alt: Contacts new record
To change how fields are displayed in the table, click the **View Form**
button to open the
:doc:`View Form Dialog `.
Let's change the displayed fields using **left**, **right**, **up**
and **down** buttons.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/view_contacts1_jampy.png
:scale: 50%
:align: center
:alt: View contacts1
In the example above, we have hidden the "firstname" field by selecting it and
pressing the **right** arrow button, and we have moved the "notes" field to be
last by selecting it and pressing the **down** button.
We can also change which fields can be used for sorting the table.
To do this, click on the button right to the **Sort fields** input and select the
corresponding column header of the table. Then save all changes by pressing **OK**.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/view_contacts_order_by_jampy.png
:scale: 50%
:align: center
:alt: view_contacts_order_by_jampy.png
To change the way the fields are displayed in the edit form, click the **Edit Form**
button to open the
:doc:`Edit Form Dialog `.
This works in a very similar way to the **View Form** above.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/edit_contacts2_jampy.png
:scale: 50%
:align: center
:alt: Edit contacts2
To see the result of our work, go to the project page, refresh it and click
the **New** button.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/contacts_view_jampy.png
:scale: 50%
:align: center
:alt: Contacts view
=======
Filters
=======
:doc:`Filters `
are used to select records from the database table according to the specified criteria.
Click the **Filters** button to open the
:doc:`Filters Dialog `.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/filters_dlg_jampy.png
:scale: 50%
:align: center
:alt: Filters dialog
Now click the **New** button and fill out the following form:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/new_filter_jampy.png
:scale: 50%
:align: center
:alt: New filter
Similarly, create a few other filters:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/all_filters_jampy.png
:scale: 50%
:align: center
:alt: All filters
When we refresh the project page, the **Filters** button appears in the
header of the "Contacts" form. Clicking this button opens the "Filters" dialog box:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/contacts_filters_jampy.png
:scale: 50%
:align: center
:alt: Contacts filters
===============================
Tutorial. Part 1. First project
===============================
Now, we’ll walk you through the creation of a basic Customer Relationship
Management (CRM) application. Please follow the steps below:
.. toctree::
:maxdepth: 1
new_project
new_catalog
lookup_fields
lookup_list
customizing_forms
indexes
filters
===========
New project
===========
We'll assume that jam.py is already installed. If not, see
:doc:`Installation `
guide how to do it.
First create a folder for the new project. In this folder, execute the
``jam-project.py`` script to create the project structure.
.. code-block:: console
$ jam-project.py
After that, run the ``server.py`` script that ``jam-project.py`` created:
.. code-block:: console
$ ./server.py
Now, to complete the creation of the project, open the web browser and go to
.. code-block:: console
127.0.0.1:8080/builder.html
to open the Application Builder. You should see the language selection dialog.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/lang_jampy.png
:scale: 50%
:align: center
:alt: Select language dialog
Use the browse button to select **English**, and click the **OK** button.
The project parameters dialog box appears.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/project_params_jampy.png
:scale: 50%
:align: center
:alt: Project params
Fill out the form as in the picture above, and click **OK**.
Now you should see the project tree in the left panel.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/adm_new_project_jampy.png
:scale: 50%
:align: center
:alt: Project Application builder
.. important::
As seen on top right corner, The Name of the application is displayed, name of the database used, application version number, and
``Jam.py`` framework ``version`` number.
Open a new tab, type
.. code-block:: console
127.0.0.1:8080
in the address bar and press Enter.
A new project appears with an empty menu.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/empty_project_jampy.png
:scale: 50%
:align: center
:alt: Empty project
===========
New catalog
===========
Let's go back to the Application builder page and create a "Customers" catalog.
A catalog corresponds to a new table in the database.
To do this, select the "Catalogs" group in the project tree and click the **New**
button on the bottom right corner of the page
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/catalogs_jampy.png
:scale: 50%
:align: center
:alt: Catalogs
In the
:doc:`Item Editor `
dialog that appears, fill in the caption and name of the new catalog. The caption
is the name that will be displayed to users, and the name is the variable name that
will be used in code (Python or JS) to refer to this catalog. The name must be a
valid Python identifier.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/customers_new_jampy.png
:scale: 50%
:align: center
:alt: New customers catalog
Then, click the **New** button on the bottom right corner of the dialog to add a new field. The
:doc:`Field Editor `
dialog appears. Type the caption and name of the
"Firstname" field, select its type (here ``TEXT`` with 30 characters)
and click the **OK** button.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/firstname_jampy.png
:scale: 50%
:align: center
:alt: New journal tasks
Similarly, add the "Lastname" and "Phone" fields. When adding the "Lastname"
field, check the **Required** attribute. This requires that the field is set
when creating a new item.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/customers_fields_jampy.png
:scale: 50%
:align: center
:alt: Customers fields
Now, to save the changes, click the **OK** button. When saving, the Application
builder created the ``CRM_CUSTOMERS`` table in the ``crm.sqlite`` database:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/customer_sql_jampy.png
:scale: 50%
:align: center
:alt: Table created
Go to the Project page and make sure it is refreshed
.. code-block:: console
127.0.0.1:8080
Then, click the **New** button to create a new customer.
Fill in the dialog, then click the **OK** button:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/customers_refresh_jampy.png
:scale: 50%
:align: center
:alt: Customers page
See also
========
:doc:`sanitizing `
=============
Lookup fields
=============
Now, we will create the "Contacts" journal and use lookup fields to connect contacts to customers. Lookup fields allow an element of one item (i.e., catalog or journal) to reference an element from an other 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. Journals, like catalogs, correspond to different tables in the database. See this :doc:`link ` for more information.
First, add a "Contact date" field of the ``DATETIME`` type,
and a "Notes" field of the ``TEXT`` type.
If "Default value" for "Contact date" was set to "CURRENT DATETIME", the field
will be filled automatically.
Then, add the
:doc:`lookup field `
"Customer", which will store a reference to a record in the "Customers" catalog.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/contacs_cutomer_fld_jampy.png
:scale: 50%
:align: center
:alt: Adding Customer field
To create a lookup field, first specify its caption and name, and leave the type
empty. Then go to the **Lookup** tab and click the button to the right of the **Lookup item** input.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/cutomers_lookup_tab_jampy.png
:scale: 50%
:align: center
:alt: Cutomer lookup tab
This brings up a list of items. Double click the "Customer" record to select it.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/cutomers_lookup_item_jampy.png
:scale: 50%
:align: center
:alt: Selecting Customer lookup item
Next, we need to specify a lookup field. This is how the customer will be located.
Here we choose "Lastname". Leave the other fields empty and press **OK**.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/lastname_lookup_jampy.png
:scale: 50%
:align: center
:alt: Lastname lookup field
Repeat this procedure to add the "Firstname" and "Phone" lookup fields.
For these fields, we specify the "Customer" field as their **Master field** attribute.
This connects them to the first "Lastname" lookup field we created, so that all three
lookup fields refer to the same customer.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/contacts_firstname_jampy.png
:scale: 50%
:align: center
:alt: contacts_firstname_jampy.png
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/phone_lookup_jampy.png
:scale: 50%
:align: center
:alt: Phone lookup field
Click the **OK** button to save the "Contacts" item.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/contacts_sql_jampy.png
:scale: 50%
:align: center
:alt: Contacts sql
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 this record will have the "Firstname" and "Phone" fields.
Refresh the project page and, in the "Contacts" page, click the **New** button.
You will see that there is a small button to the right of the "Customer" input.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/customer_input_jampy.png
:scale: 50%
:align: center
:alt: Customer input
Click on it and select a record in the "Customers" catalog: the
fields "Customer", "Firstname" and "Phone" will be filled automatically.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/customer_input_selected_jampy.png
:scale: 50%
:align: center
:alt: Customer input selected
============
Lookup lists
============
Now we create a
:doc:`lookup List `
"Status". Lookup lists are used to create "dropdown" field, with a limited
set of possible values.
Select the "Task" node in the project tree and click the **Lookup lists** button.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/lookup_lists_jampy.png
:scale: 50%
:align: center
:alt: Lookup lists
It is initially empty. Click the **New** button to create a new list.
Specify the new lookup list name and add a list of integer-text pairs:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/lookup_list_ready_jampy.png
:scale: 50%
:align: center
:alt: Lookup list entries
Save the Lookup Lists with the **OK** button, then edit the "Contacts" journal
to add the new "Status" field.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/status_field_jampy.png
:scale: 50%
:align: center
:alt: Status field
As for lookup fields, set the caption and name and leave the type empty. Then go to
the **Lookup** tab, and set the **Lookup value list** attribute to the "Status" lookup list:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/status_lookup_jampy.png
:scale: 50%
:align: center
:alt: Status lookup field
Finally, before saving, open the "Customer" field we created earlier,
and set the **Required** attribute (in the "Field" tab) and the **Typeahead** attribute
(in the "Lookup" tab). When the **Typeahead** is checked, autocompletion/typeahead is
enabled for the lookup field.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/customer_required_attr_jampy.png
:scale: 50%
:align: center
:alt: Customer attributes
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/customer_typeahead_attr_jampy.png
:scale: 50%
:align: center
:alt: Customer typeahead attribute
While we are here, set **Default value** of the "Contact date" field to "CURRENT DATETIME",
so that the date will be automatically initialised to the current date and time.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/contact_date_default_value_jampy.png
:scale: 50%
:align: center
:alt: Contact date field default value
We can likewise select a **Default value** for the "Status" field, by selecting a value
in the drop-down lists.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/status_default_value_jampy.png
:scale: 50%
:align: center
:alt: Status field default value
=======
Indexes
=======
Let's set the default sorting of records of the "Contacts" journal.
To do so, click the
:doc:`Order `
button.
By default the records are displayed in the order they were created. To change this,
move some of the column headers from the list on the right to the list on the left
using the **left** button, and change their priority using the **up** and **down**
buttons (higher priority on the top).
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/order_dlg_jampy.png
:scale: 50%
:align: center
:alt: Contacts order
In the example above, we set the default sorting to be
descending contact date.
We can create a corresponding index for the “Contacts” journal database table.
Click the **Indices** button to open
:doc:`Indices Dialog `
and then click the **New** button and specify the index in a similar way:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/index_dlg_jampy.png
:scale: 50%
:align: center
:alt: New index
=======================================
Tutorial. Part 2. File and image fields
=======================================
In this part we will demonstrate how to work with files and images in Jam.py.
Adding Image field
==================
Let's select the "Customers" catalog, Double-click it to open the
:doc:`Item Editor Dialog `
and add an image field "Photo":
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/image_field_jampy.png
:scale: 50%
:align: center
:alt: image_field_jampy.png
Now refresh the project page, click the Customers menu item and open the edit form.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/image_project1_jampy.png
:scale: 50%
:align: center
:alt: image_project1_jampy.png
Double-click the image in the editing form to select an image from the Open File
dialog box.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/select_image_jampy.png
:scale: 50%
:align: center
:alt: select_image_jampy.png
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/image_project2_jampy.png
:scale: 50%
:align: center
:alt: image_project2_jampy.png
.. note::
To clear an image, hold down the Ctrl key and double-click the image.
Let's open the
:doc:`Field Editor Dialog `
in Application Builder and set **View width** to 120 and
**Edit width** to 314 on the **Interface** tab.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/image_field2_jampy.png
:scale: 50%
:align: center
:alt: image_field2_jampy.png
.. note::
You can set the image placeholder by double-clicking on it.
In the
:doc:`View Form Dialog `
we set **Row lines** to 4 and the width of the "Photo" field to 120.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/image_view_form_jampy.png
:scale: 50%
:align: center
:alt: image_view_form_jampy.png
Now on the project page we will have:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/image_edit_jampy.png
:scale: 50%
:align: center
:alt: image_edit_jampy.png
See also
========
:doc:`accept string `
Capturing image from camera
===========================
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.
The image is uploaded automatically to the server, providing the ".png" is added
on application :doc:`Parameters ` :doc:`list of accepted values `.
Adding file field
=================
Now we add a field that will store an attachment file to the "Contacts" journal.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/file_field_jampy.png
:scale: 50%
:align: center
:alt: file_field_jampy.png
This field will be displayed in the editing form as follows:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/appendix_edit_jampy.png
:scale: 50%
:align: center
:alt: appendix_edit_jampy.png
The field input have three buttons on the right - to upload, to download and to
open a file.
Let's open the
:doc:`Field Editor Dialog `
in Application Builder and uncheck the **Download btn** check box and set
**Accept** attribute to '.pdf'.
Please review the :doc:`list of accepted values `,
before adding the values.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/appendix_interface_jampy.png
:scale: 50%
:align: center
:alt: appendix_interface_jampy.png
Let's refresh the project page, open the "Contacts" edit form and upload a file
by clicking the upload button:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/appendix_select_jampy.png
:scale: 50%
:align: center
:alt: appendix_select_jampy.png
Now we can open a file in the browser by clicking on the open button.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/appendix_field2_jampy.png
:scale: 50%
:align: center
:alt: appendix_field2_jampy.png
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/appendix_pdf_open_jampy.png
:scale: 50%
:align: center
:alt: appendix_pdf_open_jampy.png
.. 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
:doc:`project parameters `.
See also
========
:doc:`accept string `
=========================
Tutorial. Part 3. Details
=========================
In this part of the tutorial we will explain how to work with details.
Let's select the "Task/Groups" item in the project tree and click the **New**
button at the bottom right corner of the page:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/detail_group_jampy.png
:scale: 50%
:align: center
:alt: detail_group_jampy.png
Name it "Details":
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/detail_group_not_visible_jampy.png
:scale: 50%
:align: center
:alt: detail_group_not_visible_jampy.png
In the
:doc:`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:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/new_detail_jampy.png
:scale: 50%
:align: center
:alt: new_detail_jampy.png
.. note::
The most important step now is to create the field that "links" the Detail to its Master.
This is a key difference from Jam.py v5, where Detail records were handled as a legacy feature.
For example, create field "ContactID":
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/new_detail_lookup_id_jampy.png
:scale: 50%
:align: center
:alt: new_detail_lookup_id_jampy.png
The "ID" field is a :doc:`lookup field ` to "Contacts" table.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/new_detail_lookup_id_field_jampy.png
:scale: 50%
:align: center
:alt: new_detail_lookup_id_field_jampy.png
After saving the "To do list", select the "Contacts" journal and click the
**Details** button in the right pane to open the
:doc:`Details Dialog `.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/new_detail_to_do_jampy.png
:scale: 50%
:align: center
:alt: new_detail_to_do_jampy.png
Click the right arrow button to add the "To do list" to the "Contacts" details
and the **OK** button to save changes.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/details_dialog_jampy.png
:scale: 50%
:align: center
:alt: details_dialog_jampy.png
A new "To do list" item will be created as a Detail of the "Contacts" journal.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/contacts_to_do_list_jampy.png
:scale: 50%
:align: center
:alt: contacts_to_do_list_jampy.png
Select the "Contacts" journal again and click the **Edit form** button to open the
:doc:`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.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/details_to_edit_jampy.png
:scale: 50%
:align: center
:alt: details_to_edit_jampy.png
Let's update the project page and dblclick on the contact. Now we can add items
to the to-do list of the contact.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/new_to_do_item_jampy.png
:scale: 50%
:align: center
:alt: new_to_do_item_jampy.png
Click the **Groups** node in the project tree, dblclick the **Details** row and
set Visible attribute to true.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/detail_group_visible_jampy.png
:scale: 50%
:align: center
:alt: detail_group_visible_jampy.png
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.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/todo_all_jampy.png
:scale: 50%
:align: center
:alt: todo_all_jampy.png
Select the "Contacts" journal again and click the **View form** button to open the
:doc:`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.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/contacts_view_detail_jampy.png
:scale: 50%
:align: center
:alt: contacts_view_detail_jampy.png
In the project page will see that the to-do list changes when the contact changes.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/todo_completed_jampy.png
:scale: 50%
:align: center
:alt: todo_completed_jampy.png
==================
Jam.py programming
==================
Here, the basic concepts of Jam.py programming will be explained.
.. toctree::
:maxdepth: 2
task_tree
workflow
modules
interface/index
data/index
server/index
reports/index
reserved
=========
Task tree
=========
All objects of the framework represent a tree of objects. These objects are
called items.
All items of the tree have common ancestor class ``AbstractItem`` (
:doc:`client reference `
/
:doc:`server reference `
) and common attributes:.
* ``ID`` - unique in the framework ID of the item
* ``owner`` - immediate parent and owner of the item
* ``task`` - root of the task tree
* ``items`` - list of child items
* ``item_type`` - type of the item
* ``item_name`` - the name of the item that will be used in programming code
to get access to the item
* ``item_caption`` - the item name that appears to users
At 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:
* "journals" - these groups contain items with "item" ``item_type``, that can have
associated database table.
* "catalogs" - such groups also contain items that can have associated database
tables, but they can be used to create details for other items (see
:doc:`Details `
).
* "reports" - these groups contain reports - items with "report" ``item_type``,
that are used to create reports.
There can be an unlimited number of groups. We would suggest logical name for the
group, since the name is used for the drop-down menu.
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
:doc:`Demo project `
is::
/demo/
catalogs/
customers
tracks/
invoice_table
albums
artists
genres
mail
journals/
invoices/
invoice_table
invoices_client/
invoice_table
details/
invoice_table
reports/
invoice
purchases_report
analytics
system
At the root of the task tree is a task with the ``item_name`` **demo**. It has
five groups: **catalogs**, **journals**, **details**, **reports**, **analytics**
and **system**. The
**catalogs** and **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 its 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 a customer's invoice.
So there are three items with the same name
"invoice_table", detail table in Details Group, details for Journal/Invoices and
detail for Catalogs/Tracks.
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
:doc:`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
.. code-block:: js
def on_apply(item, delta, params):
customers = item.task.catalogs.customers.copy()
or just
.. code-block:: js
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 behaviour 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
:doc:`Form events `
Video
=====
The `Task tree`_ video tutorial demonstrates the task tree using
:doc:`Demo project `
.. _`Task tree`: https://youtu.be/hsSKqEh6vL4
=============
Common fields
=============
Items that have access to the database data can have common fields. They are
defined in the group they belong to:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/common_fields_jampy.png
:align: center
:alt: Common fields
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
:doc:`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.
The **Record version** field is used for :doc:`Record locking `.
=======
Dataset
=======
Jam.py framework uses a dataset concept that is very close to
datasets of `Embarcadero Delphi`_.
.. _Embarcadero Delphi: https://en.wikipedia.org/wiki/Delphi_(programming_language)
.. note::
There are other ways to read and modify the database data. You can use the
:doc:`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
:doc:`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
* :doc:`Item class ` (on the client)
* :doc:`Item class ` (on the server)
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:
.. code-block:: js
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();
}
.. code-block:: py
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
:doc:`Modifying datasets `
).
.. note::
There is a shorter way to navigate a dataset (see
:doc:`Navigating datasets `
). For example, in python, the following loops are equivalent:
.. code-block:: py
while not customers.eof():
print customers.firstname.value
customers.next()
for c in customers:
print c.firstname.value
Videos
======
`Datasets`_ and `Datasets Part 2`_ demonstrate almost all methods of working with
datasets on specific examples
.. _`Datasets`: https://youtu.be/gHTYj7h9ljI
.. _`Datasets Part 2`: https://youtu.be/1bUGmgBfrNw
=======
Details
=======
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 a 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
:doc:`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
.. code-block:: py
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
In Jam.py v5, Details have two
:doc:`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.
In Jam.py v7, the above two fields do not exist, however, they are supported for
migrated v5 application. The Jam.py v7 application will use the Lookup fields for
master as details, normally as Foreign Keys fields.
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
Examples
========
In this example from the client module of the **Invoices** item of
:doc:`Demo project `, the **Invoice_table** detail is
reopened every time the cursor of its master moves to another record.
.. code-block:: js
var ScrollTimeOut;
function on_after_scroll(item) {
clearTimeout(ScrollTimeOut);
ScrollTimeOut = setTimeout(
function() {
item.invoice_table.open(function() {});
},
100
);
}
And just as an example:
.. code-block:: py
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:
.. code-block:: js
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();
}
======
Fields
======
All items that work with database table data have a
:doc:`fields `
attribute. This attribute is a list of field objects used to represent the fields of the item’s table records.
Every field has the following attributes:
.. csv-table::
:header: Client, Server, Description
:widths: 10, 10, 80
:doc:`owner `, :doc:`owner `, "The item that owns this field."
:doc:`field_name `, :doc:`field_name `, "The name of the field that will be used in programming code to get access to the field object."
:doc:`field_caption `, :doc:`field_caption `, "The name of the field that appears to users."
:doc:`field_type `, :doc:`field_type `, "Type of the field, one of the following values: **text**, **integer**, **float**, **currency**, **date**, **datetime**, **boolean**, **blob**."
:doc:`field_size `, :doc:`field_size `, "A size of the field with type **text**"
:doc:`required `, :doc:`required `, "Specifies whether a nonblank value for a field is required."
To get access to the item dataset data, the Field class has the following properties:
.. csv-table::
:header: Client, Server, Description
:widths: 10, 10, 80
:doc:`value `, :doc:`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."
:doc:`text `, :doc:`text `, "Use this property to get or set the value of the field as text."
:doc:`lookup_value `, :doc:`lookup_value `, "Use this property to get or set lookup value, see :doc:`Lookup fields `."
:doc:`lookup_text `, :doc:`lookup_text `, "Use this property to get or set the lookup value of the field as text, see :doc:`Lookup fields `."
:doc:`display_text `, :doc:`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 :doc:`on_field_get_text ` event handler of the item that owns the field."
:doc:`raw_value `, :doc:`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. To get
access to a field of an item, use the following syntax: ``item.field_name``
.. code-block:: js
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 fields attributes of the **invoices** item in the
:doc:`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
=================
Filtering records
=================
There are three ways to define what records an item
:doc:`dataset `
will get from the database table
when the ``open`` method is called:
* to specify ``where`` parameter (option) of the ``open`` method,
* call the ``set_where`` method, before calling the ``open`` method,
* or use
:doc:`filters `.
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 Module`` 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:
.. code-block:: js
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 within the ``Server Module`` looks the following way:
.. code-block:: py
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:
.. csv-table::
:header: Filter type, Filter symbol, Constant, SQL Operator
:widths: 20, 15, 10, 70
``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`` 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}``.
The '__isnull' filter symbol is used with Boolean.
For example, at the above ``Server Module`` code:
.. code-block:: py
invoices.set_where(invoicedate__isnull = True)
See also
========
:doc:`Dataset `
:doc:`Filters `
Client
------
:doc:`open `
:doc:`set_where `
Server
------
:doc:`open `
:doc:`set_where `
=======
Filters
=======
For each item that have access to a database table a list of filter objects can
be created.
To create filters use
an :doc:`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 code
* ``filter_caption`` - the name of the filter used in the visual representation
in the client application,
* ``filter_type`` — type of the filter, see
:doc:`Filtering records `,
* ``visible`` — if the value of this attribute is ``true``, a visual
representation of this filter will be created by the
:doc:`create_filter_inputs `
method, when a ``filters`` option is not specified,
* value — a value of the filter,
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:
.. code-block:: js
invoices.filters.invoicedate1.value = new Date()
Another way to get access to the filter is to use
:doc:`filter_by_name `
method:
.. code-block:: js
invoices.filter_by_name('invoicedate').value = new Date()
See also
========
:doc:`Dataset `
:doc:`Filtering records `
Client
------
:doc:`filters `
:doc:`Filter class `
:doc:`assign_filters `
:doc:`clear_filters `
:doc:`each_filter `
:doc:`filter_by_name `
Server
------
:doc:`filters `
:doc:`Filter class `
:doc:`clear_filters `
:doc:`filter_by_name `
================
Data programming
================
.. toctree::
:maxdepth: 1
dataset
navigating_datasets
modifying_datasets
fields
common_fields
lookup_fields
filtering_records
filters
details
===================
Navigating datasets
===================
Each active dataset has a cursor, or pointer, to the current row in the dataset.
The current row in a dataset is the one whose values can be manipulated by
``edit``, ``insert``, and ``delete`` methods, and the one, whose field values,
data-aware controls on a form currently show.
You can change the current row by moving the cursor to point at a different row.
The following table lists methods you can use in application code to move to
different records:
.. csv-table::
:header: Client method, Server method, Description
:widths: 10, 10, 80
:doc:`first `, :doc:`first `, "Moves the cursor to the first row in an item dataset."
:doc:`last `, :doc:`last `, "Moves the cursor to the last row in an item dataset."
:doc:`next `, :doc:`next `, "Moves the cursor to the next row in an item dataset."
:doc:`prior `, :doc:`prior `, "Moves the cursor to the previous row in an item dataset."
In addition to these methods, the following table describes two methods that
provide useful information when iterating through the records in a dataset:
.. csv-table::
:header: Client method, Server method, Description
:widths: 10, 10, 80
:doc:`bof `, :doc:`bof `, "If the method returns true, the cursor is at the first row in the dataset, otherwise, the cursor is not known to be at the first row in the dataset."
:doc:`eof `, :doc:`eof `, "If the method returns true, the cursor is at the last row in the dataset, otherwise, the cursor is not known to be at the last row in the dataset."
Each time the cursor move to another record in the dataset the following events
are triggered:
.. csv-table::
:header: Client event, Server event, Description
:widths: 10, 10, 80
:doc:`on_before_scroll `, ``on_before_scroll``, "Occurs before an application scrolls from one record to another."
:doc:`on_after_scroll `, ``on_after_scroll``, "Occurs after an application scrolls from one record to another."
Using this methods we can navigate a dataset. For example,
on the client:
.. code-block:: js
function get_customers(customers) {
customers.open();
while (!customers.eof()) {
console.log(customers.firstname.value, customers.lastname.value);
customers.next();
}
}
on the server:
.. code-block:: py
def get_customers(customers):
customers.open()
while not customers.eof():
print customers.firstname.value, customers.lastname.value
customers.next()
Shorter ways to navigate dataset
================================
There is the :doc:`each ` method on the client that can
be used to navigate a dataset:
For example:
.. code-block:: js
function get_customers(customers) {
customers.open();
customers.each(function(c) {
if (c.rec_no === 10) {
return false;
}
console.log(c.rec_no, c.firstname.value, c.lastname.value);
});
}
On the server we can iterate dataset rows the following way:
.. code-block:: py
def get_customers(customers):
customers.open()
for c in customers:
if c.rec_no == 10:
break
print c.firstname.value, c.lastname.value
Both functions will output customer names for the first 10 records in the dataset.
In both cases the **c** and **customers** are pointers to the same object.
=============
Lookup fields
=============
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.
Lookup item based lookup field
==============================
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.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/albums_edit_form_jampy.png
:align: center
:alt: albums_edit_form_jampy.png
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
:doc:`Field Editor Dialog `:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/artist_field_jampy.png
:align: center
:alt: Lookup field
For such fields **Lookup item** and **Lookup field** must be specified in the
:doc:`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:
.. csv-table::
:header: Client, Server, Description
:widths: 10, 10, 80
:doc:`value `, :doc:`value `, "A value, that is stored in the item table, that is a reference to a record in the lookup item table."
:doc:`lookup_value `, :doc:`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.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/master_field_example_jampy.png
:align: center
:alt: Master field example
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
:doc:`lookup_field `
attribute to the field. and
triggers
:doc:`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:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/tracks_lookup_field_jampy.png
:align: center
:alt: tracks_lookup_field_jampy.png
To set up such a field use **Lookup field 2** and **Lookup field 3** attributes.
Value list
==========
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
:doc:`Demo project ` has a **Lookup value list** attribute set
to the MediaTypes lookup list:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/mediatype_field_jampy.png
:align: center
:alt: MediaType field definition
Use the :doc:`Lookup List Dialog ` of the task to define
such lookup lists.
See also
========
:doc:`Lookup fields `
:doc:`Lookup lists `
==================
Modifying datasets
==================
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
:doc:`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:
.. csv-table::
:header: Client, Server, Description
:widths: 10, 10, 80
:doc:`item_state `, :doc:`item_state `, "Indicates the current operating state of the item dataset."
:doc:`is_new `, :doc:`is_new `, "Returns true if the item dataset is in *insert* state."
:doc:`is_edited `, :doc:`is_edited `, "Returns true if the item dataset is in *edit* state."
:doc:`is_changing `, :doc:`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:
.. csv-table::
:header: Client, Server, Description
:widths: 20, 20, 100
:doc:`edit `, :doc:`edit `, "Puts the item dataset into edit state."
:doc:`append `, :doc:`append `, "Appends a record to the end of the dataset, and puts the dataset in *insert* state."
:doc:`insert `, :doc:`insert `, "Inserts a record at the beginning of the dataset, and puts the dataset in *insert* state."
:doc:`post `, :doc:`post `, "Saves the new or altered record, and puts the dataset in *browse* state."
:doc:`cancel `, :doc:`cancel `, "Cancels the current operation and puts the dataset in *browse* state."
:doc:`delete `, :doc:`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.
.. csv-table::
:header: Client, Server, Description
:widths: 20, 20, 100
:doc:`log_changes `, :doc:`log_changes `, "Indicates whether to log data changes."
:doc:`apply `, :doc:`apply `, "Sends all updated, inserted, and deleted records from the item dataset to the server for writing to the database."
===================
Data-aware controls
===================
To create a table to display an item's dataset use
:doc:`create_table `
method:
.. code-block:: js
item.create_table(item.view_form.find(".view-table"), table_options);
To create data controls to edit fields of the of the dataset use
:doc:`create_inputs `
method:
.. code-block:: js
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
:doc:`disable_controls `
and
:doc:`enable_controls `
methods respectively.
Videos
======
`Data aware controls`_
.. _`Data aware controls`: https://youtu.be/fMTq8P4XdGw
===========
Form events
===========
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 yet
* ``on_view_form_shown`` -the event is triggered when the the form has been shown
* ``on_view_form_close_query`` - the event is triggered when an attempt is made to close the form
* ``on_view_form_closed`` - the event is triggered when the form has been closed
* ``on_view_form_keydown`` - the event is triggered when the keydown event occurs for the form
* ``on_view_form_keyup`` - the event is triggered when the keyup event occurs for the form
For 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
:doc:`view `
method of corresponding task tree item, this method creates a form using its HTML
form template and triggers first the
:doc:`on_view_form_created ` event of
the task.
When you create a new project, the task client module already contains the
code, including the
:doc:`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
:doc:`view_form `
and
:doc:`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:
.. code-block:: js
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
:doc:`on_view_form_created `
event handler of the item group and.
:doc:`on_view_form_created `
of the item if they are defined:
.. code-block:: js
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
:doc:`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
:doc:`on_view_form_created `
event handler is defined. In it we
change the height attribute of the
:doc:`table_options `
, create the copy of the
invoice_table set its attributes and call its
:doc:`create_table `
method that creates a table to display its data.
.. code-block:: js
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
:doc:`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.
The order in which events are generated depends on the type of event.
Close query events
==================
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
:doc:`on_edit_form_close_query `
event handler in the task client module:
.. code-block:: js
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:
.. code-block:: js
function on_edit_form_close_query(item) {
item.cancel()
return true;
}
Keydown, keyup events
=====================
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
:doc:`on_edit_form_keyup `
event handler in the task client module:
.. code-block:: js
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:
.. code-block:: js
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.
All other events
================
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.
.. code-block:: js
_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;
}
}
}
=============
Form examples
=============
Currently, the Framework uses `Bootstrap 5`_ with a simple and easy to use grid
system that uses 12 columns and allows you to create any kind of layouts.
It is responsive and have many components, such as dropdowns,
dropdown buttons, button groups, navs, navbars, tabs, breadcrumbs, badges,
progress bars, etc.
.. _`Bootstrap 5`: https://getbootstrap.com/docs/5.0/
Default edit form template
--------------------------
This template is used for creating edit forms, if an item and its owner don't
have their own edit form template
.. code-block:: html
The event below is located in the task client module and is triggered
for any item whose edit form has just been created.
It uses the
:doc:`create_inputs `
method to create inputs in the div with class "edit-body". But before that it
checks if ``init_inputs`` function is defined in the item's client module, that
can be used to specify the options parameter of the method.
It then assigns the jQuery events to the OK and Cancel buttons.
.. code-block:: js
function on_edit_form_created(item) {
var options = {
col_count: 1
};
if (item.init_inputs) {
item.init_inputs(item, options);
}
item.create_inputs(item.edit_form.find(".edit-body"), options);
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()
});
}
The edit form for **Albums** catalog looks as follows:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/default_edit_form_template_jampy.png
:align: center
:alt: Default edit form template
.. note::
If there are no buttons with the corresponding ids,
the code above does not generate exceptions.
If you want to overwrite JQuery events for buttons declared in the client
module of the task, in the corresponding event of the client module of the
item, you can do this using the jQuery ``off`` method:
.. code-block:: js
item.edit_form.find("#ok-btn")
.off('click.task')
.on('click', function() { some_other_function(item) });
If there is no corresponding container in the form, the ``create_inputs``
method does nothing.
Edit form template with tabs
----------------------------
This code example uses the Bootstrap 3 tabs. It needs update due to
href not used for Boostrap 5:
.. code-block:: html
The following event handler is declared in the **Customers** item client module.
It creates input controls for panes, corresponding to tabs:
.. code-block:: js
function on_edit_form_created(item) {
item.edit_form.find('#customer-tabs a').click(function (e) {
e.preventDefault();
$(this).tab('show');
});
item.create_inputs(item.edit_form.find("#cust-name"),
{fields: ['firstname', 'lastname', 'company', 'support_rep_id']}
);
item.create_inputs(item.edit_form.find("#cust-address"),
{fields: ['country', 'state', 'address', 'postalcode']}
);
item.create_inputs(item.edit_form.find("#cust-contact"),
{fields: ['phone', 'fax', 'email']}
);
}
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/edit_form_template_with_tabs_jampy.png
:align: center
:alt: Edit form template with tabs
All of the above can be achieved with no code or template within the ``Application Builder``.
The same form in default format:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/customer_edit_jampy.png
:align: center
:alt: Default edit form
Edit form template using grid layout
------------------------------------
This example uses the Bootstrap grid system:
.. code-block:: html
or:
.. code-block:: html
.. code-block:: js
function on_edit_form_created(item) {
item.edit_options.width = 900;
item.create_inputs(item.edit_form.find("#edit-top"), {
fields: ['name']
});
item.create_inputs(item.edit_form.find("#edit-left"), {
fields: ['album', 'artist', 'composer', 'media_type']
});
item.create_inputs(item.edit_form.find("#edit-right"), {
fields: ['genre', 'milliseconds', 'bytes', 'unitprice']
});
}
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/edit_form_tepmlate_layout_jampy.png
:align: center
:alt: Edit form template using grid layout
With **named** headers around each section, like “Track Info” / “Technical Info”:
.. code-block:: html
General Information
Track Info
Technical Info
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/edit_form_tepmlate_layout_headers_jampy.png
:align: center
:alt: Edit form template using grid layout with headers
Catalogs view form template
-----------------------------------
In this example there is a div with class "form-header".
The element with id "form-title" is used in the
:doc:`on_view_form_created `
method of the task to display the caption of an item and assign to it a JQuery
onclick event to execute view method to recreate the view form.
The elements with id "selected-div" and "search-form" are used in the
:doc:`on_view_form_created ` of the
catalogs group to display current value of a lookup field when the right button
is clicked to select a value and to implement search functionality of catalogs
correspondingly
The div with class "view-table" is used in the
:doc:`on_view_form_created `
event handler of the task to create a table to display item's data by using
:doc:`create_table `
method:
.. code-block:: js
if (item.view_form.find(".view-table").length) {
if (item.init_table) {
item.init_table(item, table_options);
}
item.create_table(item.view_form.find(".view-table"), table_options);
item.open(true);
}
The div with id "report-btn" is used in the
:doc:`on_view_form_created `
event handler of the task to fill dropdown button menu items with reports defined
in the
:doc:`Reports Dialog `
of the item (if they exist).
.. code-block:: html
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/catalogs_view_teplate_jampy.png
:align: center
:alt: Catalogs view form template
View form template with buttons at the top
------------------------------------------
In this example the form footer div is removed and buttons are placed to the
form header div. The **Actions** dropdown button is created. The code is the same
as in previous example.
.. code-block:: html
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/view_template_btns_top_jampy.png
:align: center
:alt: View form template with buttons at the top
View form template with detail
------------------------------
In this example the div with class "view-table" is removed and added two
divs "view-master" and "view-detail" tables for master and detail items
are created in the
:doc:`on_view_form_created `
event handler declared in the client module of **Invoices** journal:
.. code-block:: js
function on_view_form_created(item) {
var height = $(window).height() - $('body').height() - 200 - 10;
if (height < 200) {
height = 200;
}
item.filters.invoicedate1.value = new Date(new Date().setYear(new Date().getFullYear() - 1));
item.create_table(item.view_form.find(".view-master"), {
height: height,
sortable: true,
show_footer: true,
row_callback: function(row, it) {
var font_weight = 'normal';
if (it.total.value > 10) {
font_weight = 'bold';
}
row.find('td.total').css('font-weight', font_weight);
}
});
item.invoice_table.create_table(item.view_form.find(".view-detail"), {
height: 200 - 4,
dblclick_edit: false,
column_width: {'track': '25%', 'album': '25%', 'artists': '10%'}
});
item.open(true);
}
.. code-block:: html
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/invoices_view_form_tempate_jampy.png
:align: center
:alt: Invoices view form template
============
Form options
============
For each type of form an item has an attribute that controls the modal form
behavior:
* :doc:`view_options `
* :doc:`edit_options `
* :doc:`filter_options `
* :doc:`param_options `
This is an object that has the following attributes, specifying 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
:doc:`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 button
* ``close_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 focus
* ``template_class`` - if specified, the div with this class will be searched in
the task
:doc:`templates `
attribute and used as a form html template when creating a form
* ``label_on_top`` - the label displayed on top
The
:doc:`edit_options `
has a ``fields`` attribute, that specify a list of field names that the
:doc:`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
:doc:`Edit Form Dialog `
in the Application builder.
The
:doc:`view_options `
has a ``fields`` attribute, that specify a list of field names that the
:doc:`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
:doc:`View Form Dialog `
in the Application builder.
The width of the modal form, created in the following example, will be 700 px.
.. code-block:: js
function on_edit_form_created(item) {
item.edit_options.width = 700;
}
==============
Form templates
==============
Form templates of the project are located in the
:doc:`templates.html `
file. This file is located in the root directory of a project,
and accessed/modified by click on ``templates [F9]`` on :doc:`Task ` tree.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/forms_template_jampy.png
:align: center
:alt: Forms Template
When
:doc:`load `
method is executed, the file is processed and stored in the
:doc:`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
:doc:`item_name `
of the item and ``suffix``
is the form type: view, edit, filter, param.
For example:
.. code-block:: html
...
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:
.. code-block:: html
...
If an item doesn't have a form template then the form template of its owner, if
defined, will be used.
So the template
.. code-block:: html
...
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
.. code-block:: html
...
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:
.. code-block:: html
There are more template examples in the
:doc:`Form examples `
section.
=====
Forms
=====
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
:doc:`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
:doc:`insert_record `
and
:doc:`edit_record `
methods will be executed. These methods create an item edit form.
Forms are based on HTML
:doc:`form templates `
that determine their layout. Form templates are defined in the
:doc:`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:
.. code-block:: html
You can define your own form templates to create your own custom forms. See
:doc:`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 :doc:`Form events ` for details.
Below is an example of the ``on_edit_form_created`` event handler of the task:
.. code-block:: js
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 ``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
:doc:`cancel_edit `
and
:doc:`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
:doc:`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
:doc:`edit_options `,
create tables using the
:doc:`create_table `
method and so on.
Then
the
:doc:`create_inputs `
method is called to create inputs in the element with class "edit-body"
Finally,
:doc:`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.
=======================
Client side programming
=======================
.. toctree::
:maxdepth: 1
index_html
templates_html
initializing_application
forms
form_templates
form_events
form_options
form_examples
data_controls
==========
Index.html
==========
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
and accessed/modified by click on ``index.html [F10]`` on :doc:`Task ` tree.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/index_html_jampy.png
:align: center
:alt: index.html
The html file contains 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
.. code-block:: html
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
.. code-block:: html
For forms templates, please see
:doc:`Forms `
and
:doc:`Form templates ` for details.
At the end of the file there is a following script:
.. code-block:: html
In this script the
:doc:`load `
method of the task, that has been created when *jam.js* file was loaded, is
called that loads information about the
:doc:`task tree `
from the server and, based on this information, builds its tree, loads modules,
assigns event handlers to its items and triggers
:doc:`on_page_loaded `
event. See
:doc:`Initializing application `
==============
templates.html
==============
When some method creates a form, the application finds a corresponding html template in this file.
This file is located in the root directory of a project
and accessed/modified by click on ``templates.html [F9]`` on :doc:`Task ` tree.
You can define your own form templates to create your own custom forms. See
:doc:`Form templates `.
The default ``templates.html`` content:
.. code-block:: html
========================
Initializing application
========================
The
:doc:`on_page_loaded `
event is the first event triggered by an application on the client.
The new project uses
:doc:`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.
.. code-block:: js
function on_page_loaded(task) {
$("title").text(task.item_caption);
$("#app-title").text(task.item_caption);
if (task.small_font) {
$('html').css('font-size', '14px');
}
if (task.full_width) {
$('#container').removeClass('container').addClass('container-fluid');
}
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();
});
}
$('#container').show();
task.create_menu($("#menu"), $("#content"), {
splash_screen: '
Jam.py Demo Application
',
view_first: true
});
This event handler uses JQuery to select elements from the
:doc:`index.html ` to set their attributes and assign events.
.. code-block:: html
Finally, the
:doc:`create_menu `
method of the task is called to dynamically create the main project menu.
====================
Working with modules
====================
For every item of the project :doc:`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
:doc:`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
:doc:`code editor `
In the
:doc:`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.
.. code-block:: js
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.
.. code-block:: js
function on_edit_form_created(item) {
task.customers.on_edit_form_created(item);
}
==============================
Client-side report programming
==============================
To print a report on the client use the
:doc:`print `
method.
As a result of calling this function, the client calls
:doc:`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 :doc:`Forms `).
This method, after creating the form, triggers the following events:
* :doc:`on_param_form_created `
of the task.
* :doc:`on_param_form_created `
of the report group that owns the report, if one is defined
* :doc:`on_param_form_created `
of the report, if one is defined.
The default code has the
:doc:`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
:doc:`process_report `
method.
.. code-block:: js
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
:doc:`process_report `
method triggers
* :doc:`on_before_print_report `
event handler of the report group
* :doc:`on_before_print_report `
event handler of the report
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
:doc:`extension `
attribute is defined:
.. code-block:: js
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:
.. code-block:: js
function on_before_print_report(report) {
report.id.value = report.task.invoices.id.value;
}
After that the
:doc:`process_report `
method sends asynchronous request to the server to generate the report
(see :doc:`Server-side programming `).
The server returns to the method an url to a file with generated report.
The method then checks if the
:doc:`on_open_report ` event handler of
the report group is defined. If this events handler if defined calls it,
otherwise checks the
:doc:`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
:doc:`extension `
attribute) opens the report in the browser or saves it to disc.
=================
Creating a report
=================
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.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/new_report_jampy.png
:align: center
:alt: Creating a report
If a **visible** checkbox is set, the default code adds the report to the
**Reports** menu of the project.
===================
Programming reports
===================
.. toctree::
:maxdepth: 1
templates
creating_report
report_parameters
client_side_programming
server_side_programming
================
Report templates
================
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.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/template_jampy.png
:align: center
:alt: Invoice report template
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**.
=================
Report parameters
=================
You can specify the parameters of the report. For example, the
**Customer purchases** report of the Demo project has three parameters.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/parameters_jampy.png
:align: center
:alt: Report 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.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/report_param_jampy.png
:align: center
:alt: Report param
The below can be specified:
* **Caption** - the name of the parameter that appears to users
* **Name** - the name of the parameter will be used in programming code to get
access to the parameter object.
* **Type** - the data type of the parameter
* **Lookup item** - the item to select the parameter value from
* **Lookup field** - the field in the lookup item
* **Typeahead** - autocompletion/typeahead is enabled for the lookup field.
* **Multiple selection** - Multiple selection is enabled.
* **Select all enabled** - Select all is enabled.
* **Lookup value list** - Lookup value list selection.
* **Required** - if this checkbox is checked and **Visible** attribute is set,
the client application will require a users to specify the parameter value
before printing the report
* **Alignment** - specifies how a value of the parameter will be aligned in the
input element
* **Placeholder** - use this attribute to specify the placeholder that will be
displayed by the field input.
* **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 mark, a pop-up window appears displaying this message.
* **Visible** - the client application creates a form to specify the parameters
before printing the report. If this checkbox is checked, the input element for
this parameter will appear in the form
It it possible to create a lookup parameter. For example, the **Customer purchases**
report has a **Customer** parameter that can be selected from **Customers**
catalog:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/lookup_param_jampy.png
:align: center
:alt: Lookup parameter
In this case we should specify:
* **Lookup item** - the item to select the parameter value from
* **Lookup field** - the field in the lookup item
Form for setting the parameters of **Customer purchases** report is as follows:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/customer_purchases_params_jampy.png
:align: center
:alt: Customer purchases parameters example
==============================
Server-side report programming
==============================
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
:doc:`generate `
method.
This method triggers the
:doc:`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:
.. code-block:: py
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
:doc:`copy `
method to create a copy of the invoices journal.
.. code-block:: py
invoices = report.task.invoices.copy()
We create the copy because multiple users can simultaneously generate the same
report in parallel threads.
Then we call the set_where method of the copy:
.. code-block:: py
invoices.set_where(id=report.id.value)
where report.id.value is report id parameter, the value of which we set in the
:doc:`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
:doc:`open `
method, we obtain the records on the server. After that the
:doc:`print_band `
method is used to print title band:
.. code-block:: py
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
:doc:`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.
==============
Reserved words
==============
Since Jam.py is Python and JavaScript language framework, valid Python and JavaScript identifiers
should be used.
In addition, for all supported databases, their own valid identifiers should be used.
The file containing example list is ``keywords.py``, with below content:
.. code-block:: py
keywords = [
"BADGE",
"LABEL",
"HIDDEN",
"PROGRESS",
"TASK"
]
=======================
Server side programming
=======================
In most cases, the client sends a request to the server when following methods
of an item are executed:
* :doc:`open `
* :doc:`apply `
* :doc:`print `
* :doc:`server `
In these cases the client sends to the server the
:doc:`ID `
of the item's task, the
:doc:`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
:doc:`environ `
and
:doc:`session `
attributes that store context of the current request.
The most common server events are:
* :doc:`on_created ` - The event is triggered by the task when it has just been created by the server application. It can be used to initialize the project.
* :doc:`on_apply events ` - These events are triggered when the ``apply`` method of the item is called on the :doc:`client ` or the :doc:`server `
* :doc:`on_open_events ` - These events are triggered when the ``open`` method of the item is called on the :doc:`client ` or the :doc:`server `
* :doc:`on_generate ` - "The event is triggered when the :doc:`print ` method of a report is called on the client.
.. note::
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
:doc:`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
:doc:`task tree `
and will be destroyed by Python garbage collector when no longer needed.
.. toctree::
:maxdepth: 1
on_apply_events
on_open_events
===============
on_apply events
===============
When the ``apply`` method of the item is called
on the :doc:`client ` or the
:doc:`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
:doc:`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:
.. code-block:: py
#...
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
:doc:`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
Example
=======
:doc:`Here is an example how on_apply can be used `
==============
on_open_events
==============
When the ``open`` method of the item is called
on the :doc:`client ` or the
:doc:`server `, the server application
executes the following code:
.. code-block:: py
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
:doc:`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
Example
=======
:doc:`Here is an example how on_open can be used `
========
Workflow
========
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
:doc:`task tree `.
The the Jam.py workflow is the following:
* When server.py is run it creates WSGI application that, in its own turn,
creates the Application builder task tree.
* The Project task tree is created on the server by Application builder after the
server receives first request from the Project client. To do so, the
Application builder uses metadata stored in admin.sqlite database in the root folder
of the project. After creating a task tree the server application triggers the
:doc:`on_created `
event, that can be used to initialize the server task tree.
* When an application on the client (Application builder or Project) is first run in
the browser (after ``builder.html`` or ``index.html`` have been loaded) the
empty task object is built that sends to the server a request to initialize
itself.
* If the project
:doc:`safe mode `
parameter is set, the framework checks if a user is logged, before executing
any request. If not, then the
application on the client creates a login form, and after the user inputs its
login and password, the client task sends the server a request to login.
* After successful login or if the project
:doc:`safe mode `
parameter is not set, the server sends the client information about the
requested task. The task on the client builds its tree, based on this
information, assigns event handlers to its objects and executes
:doc:`on_page_loaded `
event handler.
* In this event handler a developer should attach JQuery event handler
functions to HTML elements of the DOM, defined in the ``index.html`` file.
In these functions a developer can use methods of items of the
:doc:`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
:doc:`Client side programming `.
* Items of the task tree, that have corresponding database tables, have methods
to read and write data in the server database. See
:doc:`Data programming `.
* The report items generate the reports on the server, based on the LibrOffice
templates. See
:doc:`Programming reports `.
* All the items, whose methods generate a request to the server, do it the
following way: they call the method of the task that sends to the server the
:doc:`ID `
of the task, the
:doc:`ID `
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. These server methods could trigger their own
events that can override the default behavior. See
:doc:`Server side programming `
Video
=====
`Form events`_ and `Client-server interactions`_ video tutorials illustrate
the workflow of Jam.py project.
.. _`Form events`: https://youtu.be/DY463lcv0R4
.. _`Client-server interactions`: https://youtu.be/nLOhdA2FX0I
==========
Jam.py FAQ
==========
.. toctree::
:maxdepth: 1
faq_catalogs_vs_journals
faq_howto_upgrade_existing_project
faq_using_other_libraries
faq_print_ods_pdf
====================================================
What is the difference between catalogs and journals
====================================================
When a new project is created, its
:doc:`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 :doc:`Groups `.
We created them to distinguish between two types of data items:
* data items that contain information of catalog type
such as customers, organizations, tracks, etc. - **Catalogs**
* data items that store information about events
recorded in some documents, such as invoices, purchase orders, etc. - **Journals**
===============================
What are foreign keys used for?
===============================
Foreign keys that you can create in the Jam.py V5 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
:doc:`Item Editor Dialog `
) for the lookup field to appear in the
:doc:`Foreign Keys Dialog `
The creation/deleting support for foreign keys is dropped in Jam.py V7.
====================================================================
How to upgrade an already created project to a new version of jampy?
====================================================================
To upgrade an existing V7 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 flavour of Unix, enter the
command:
.. code-block:: console
sudo pip install --upgrade jam.py-v7
If you're using Windows, start a command shell with administrator privileges
and run the command
.. code-block:: console
pip install --upgrade jam.py-v7
To migrate v5 project to v7 project, please see
:doc:`How to migrate v5 project to v7`
=======================================================
When printing a report I get an ods file instead of pdf
=======================================================
When a report is generated the server application first creates an ods file.
If
:doc:`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.
===========================================
Can I use other libraries in my application
===========================================
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
On the server side you can import python libraries to your modules.
For example the mail item server module import smtplib library to send emails:
.. code-block:: py
import smtplib
======
How to
======
Here is a useful code that you can use in your applications:
.. toctree::
:maxdepth: 2
how_to_install_on_windows
how_to_implement_a_many-to-many_relationship
how_to_migrate_development_to_production
how_to_migrate_to_another_database
deploy/index
global_scripts
how_to_validate_field_value
how_to_add_a_button_to_a_form
how_to_execute_script_from_client
how_to_change_style_and_attributes_of_elements
how_to_create_a_custom_menu
to_append_a_record_without_view_form
how_to_prohibit_changing_record
how_to_link_two_tables
how_change_field_value_of_selected_records
how_to_save_edit_form_without_closing_it
how_to_save_changes_to_two_tables_in_same_transaction
how_to_prevent_duplicate_values
how_to_multitenancy
how_to_use_jam_py_with_existing_database
how_to_data_from_other_database_tables
how_to_request_from_other_application
how_to_calculations_in_the_background
how_to_details_inside_details
export_import_csv_files
authentication/index
how_to_migrate_to_v7
how_to_write_tests
how_to_cascade_delete_records
how_to_create_a_forms_flow
================================
How to install Jam.py on Windows
================================
.. admonition:: 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.
.. _Django Docs: https://docs.djangoproject.com/en/5.2/howto/windows/
Install Python
==============
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::
...\> python --version
About ``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.
.. _pip: https://pypi.org/project/pip/
.. _virtualenvironment:
Setting up a virtual environment
================================
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::
...\> python -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.
Install Jam.py
==============
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::
...\> python -m pip install jam.py-v7
This will download and install the latest Jam.py release.
For Python verson => 3.13, please install ``standard-imghdr`` as well::
...\> python -m pip install standard-imghdr
After the installation has completed, you can verify your Jam.py installation
by executing ``pip list`` in the command prompt.
We can now proceed with creating a new :doc:`project <../intro/new_project>`.
Common pitfalls
===============
* 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-v7
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 the following::
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 the following::
...\> Directory of C:\Users\yourser\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\Scripts
13/04/2023 02:59 PM .
13/04/2023 02:59 PM ..
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
Installing WLS
==============
Invoking installation::
...\> wsl --install
The output might be similar to the following::
Installing: Virtual Machine Platform
Virtual Machine Platform has been installed.
Installing: Windows Subsystem for Linux
Windows Subsystem for Linux has been installed.
Installing: Ubuntu
Ubuntu has been installed.
The requested operation is successful. Changes will not be effective until the system is rebooted.
Now, we have a development environment with Ubuntu, and we can proceed with Jam.py installation as usual for Linux.
===============================================
How to give user ability to change the password
===============================================
First we create a "Change password" item. While creating it, we set
the "Virtual table" and "Visible" attributes to false in the
:doc:`Item Editor Dialog `.
Add to 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``:
.. code-block:: html
and in the task client module
:doc:`on_page_loaded `
event handler add the following code:
.. code-block:: js
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:
.. code-block:: js
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. 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:
.. code-block:: py
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'
The :doc:`session ` property info is used to get the ``id`` of the current user.
After changing the password, the client reloads.
===========================================
How to authenticate from custom users table
===========================================
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:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/users_fields_jampy.png
:align: center
:alt: users_fields_jampy.png
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
:doc:`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
:doc:`Roles `
We 'll have to sycronize this roles in the future.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/roles_lookup_list_jampy.png
:align: center
:alt: roles_lookup_list_jampy.png
In the
:doc:`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
:doc:`View Form Dialog `
and
:doc:`Edit Form Dialog `
In the User server module we define the following
:doc:`on_apply `
event handler:
.. code-block:: py
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
:doc:`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.
.. code-block:: js
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
:doc:`on_login `
event handler in the task server module:
.. code-block:: py
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
:doc:`Parameters `
===============================
How to create registration form
===============================
This part is relevant for Jam.py V5. For V7, please visit:
https://groups.google.com/g/jam-py/c/DwALkbBsFcw/m/MujcOlgYAAAJ
or download example from here:
https://drive.google.com/file/d/1Gty1bC2I3srFo9XeQ0dXdAdnYeJbwoeB/view?usp=drive_link
In this topic we'll assume that you have created a :doc:`users ` item from the
previous topic.
Now we create a *register.html* file.
It contains a registration form:
.. code-block:: html
and a javascript code:
.. code-block:: js
$(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
:doc:`on_ext_request `
event.
The task server module has the following ``on_ext_request`` event handler:
.. code-block:: py
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.
See also
========
:doc:`on_ext_request `
==============
Authentication
==============
In the Jam.py repository there is the "Authentication" project
:doc:`export `
file.
This project demonstrates the first three topics of this section.
https://jam-py.com/repository/auth.zip
You can download it, create a new project and
:doc:`import `
this file.
.. toctree::
:maxdepth: 1
how_to_authenticate_from_custom_users_table
how_to_create_registration_form
how_give_user_ability_to_change_password
==================================================
A step-by-step guide to deploy a Jam.py on the AWS
==================================================
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:
.. code-block:: console
sudo apt-get update
* Install pip:
.. code-block:: console
sudo apt-get install python3-pip
* Install jam.py:
.. code-block:: console
sudo pip3 install jam.py
* Install Apache:
.. code-block:: console
sudo apt-get install apache2 apache2-utils libexpat1 ssl-cert
* Install mod-wsgi:
.. code-block:: console
sudo apt-get install libapache2-mod-wsgi-py3
* Restart Apache:
.. code-block:: console
sudo /etc/init.d/apache2 restart
* Move here:
.. code-block:: console
cd /var/www/html/
* Create directory:
.. code-block:: console
sudo mkdir [appname]
* Move here:
.. code-block:: console
cd [appname]
* Create app:
.. code-block:: console
sudo jam-project.py
* Check it's there:
.. code-block:: console
ls
* Create the config:
.. code-block:: console
sudo nano /etc/apache2/conf-available/wsgi.conf
* Paste the following
.. code-block:: apache
WSGIScriptAlias / /var/www/html/[appname]/wsgi.py
WSGIPythonPath /var/www/html/[appname]
Require all granted
Alias /static/ /var/www/html/[appname]/static/
Require all granted
* Exit and save
* Give file permissions to apache:
.. code-block:: console
sudo chmod 777 /var/www/html/[appname]
* Give ownership to apache:
.. code-block:: console
sudo chown -R www-data:www-data /var/www
* Enable wsgi:
.. code-block:: console
sudo a2enconf wsgi
* Restart apache:
.. code-block:: console
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:
.. code-block:: console
nano /var/log/apache2/error.log
*This was initially published by Simon Cox on*
https://groups.google.com/forum/#!msg/jam-py/Zv5JfkLRFy4/22tolZ-hAQAJ
=======================================
How to deploy project on PythonAnywhere
=======================================
* Use pip to install Jam.py. To do this, open up a new **Bash** console from the **Consoles** Tab and run:
.. code-block:: console
mkvirtualenv --python=/usr/bin/python3.13 my-virtualenv # use whichever python version you prefer
pip install jam.py-v7
The above **mkvirtualenv** command might display:
.. code-block:: console
created virtual environment CPython3.10.5.final.0-64 in 8486ms
creator CPython3Posix(dest=/home/username/.virtualenvs/my-virtualenv, ...
The **dest** path is used for **Virtualenv** section. For every user, this will be different.
* 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:
* Source code: */home/username/project_folder*
* Working directory: */home/username/project_folder*
* WSGI configuration file: */var/www/username_pythonanywhere_com_wsgi.py* file,
delete everything and add just the following code:
.. code-block:: py
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)
In the **Static files** section, specify the Jam.py static files location and
the path. For example:
* URL: */static/*
* Directiory: */home/username/project_folder/static*
In the **Virtualenv** section, specify the virtual environment path created
with above **mkvirtualenv** command. For example:
* /home/username/.virtualenvs/my-virtualenv/
In the **Force HTTPS** section, enable HTTPS.
* Reload the server on **Reload** section.
* To debug the process, review the logs on **Logs** section.
=====================================================
How to deploy jam-py app at Linux Apache http server?
=====================================================
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 :
.. code-block:: console
apt-get install libapache2-mod-wsgi
* Enable ssl, wsgi module for apache:
.. code-block:: console
a2enmod ssl wsgi
* Create a custom file for jam-py app, ie /etc/apache2/sites-available/test.conf,
for example (still wip):
.. code-block:: apache
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
Options +ExecCGI
SetHandler wsgi-script
AddHandler wsgi-script .py
Order deny,allow
Allow from all
Require all granted
Order deny,allow
Allow from all
# comment the following for ubuntu <13
Require all granted
# comment the following for ubuntu < 13
Require all granted
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):
.. code-block:: console
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:
.. code-block:: console
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:
.. code-block:: apache
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
Options +ExecCGI
SetHandler wsgi-script
AddHandler wsgi-script .py
Order deny,allow
Allow from all
Require all granted
Order deny,allow
Allow from all
# comment the following for ubuntu <13
Require all granted
# comment the following for ubuntu < 13
Require all granted
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
==============================================
How to do with Nginx with Gunicorn or uvicorn?
==============================================
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)
.. code-block:: console
python /usr/bin/gunicorn --chdir /path/to/jam/App wsgi
or from /path/to/jam/App:
.. code-block:: console
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:
.. code-block:: console
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
For ``uvicorn``, we need to modify the wsgi.py file and also install ``asgiref``.
Lets call it asgi.py with below content:
.. code-block:: py
from jam.wsgi import create_application
from asgiref.wsgi import WsgiToAsgi
application = WsgiToAsgi(create_application(__file__))
To start jam.py on ``localhost`` and port 8000:
.. code-block:: console
uvicorn asgi:application
INFO: Started server process [16576]
INFO: Waiting for application startup.
INFO: ASGI 'lifespan' protocol appears unsupported.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Nginx:
comment out default location in /etc/nginx/sites-enabled/default (Linux Mint):
.. code-block:: nginx
#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:
.. code-block:: nginx
# 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`` or ``uvicorn``
server address and port:
.. code-block:: nginx
# 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
.. code-block:: console
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, ``certificates``:
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):
.. code-block:: nginx
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
=============
How to deploy
=============
.. toctree::
:maxdepth: 1
how_to_deploy_project_on_pythonanywhere
a_step-by-step_guide_to_deploy_a_jam_py_on_the_aws
how_to_deploy_to_linux_apache
how_to_do_with_gunicorn
=================================
Export to / import from csv files
=================================
First, in the client module of the item we create two buttons that execute the
corresponding functions when you click on them:
.. code-block:: js
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_scv', 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_scv', [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:
.. code-block:: py
import os
import csv
def export_scv(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_scv(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:
.. code-block:: py
import os
import csv
def export_scv2(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_scv2(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()
==================================================
How do I write functions which have a global scope
==================================================
Each function defined in the server or client module of an item becomes an
attribute of the item.
Thus, using the
:doc:`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.
.. code-block:: js
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
:doc:`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:
.. code-block:: js
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.
==========================================
How change field value of selected records
==========================================
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.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/set_media_type_jampy.png
:align: center
:alt: set_media_type_jampy.png
First we set the multiselect attribute of the
:doc:`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
:doc:`on_view_form_created `
event handler in the client module of "Tracks".
.. code-block:: js
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
:doc:`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
:doc:`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
:doc:`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
:doc:`append_record `
method, we dynamically assign the
:doc:`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
:doc:`post `
method to check that the media type value is set, if exception
is raised we call
:doc:`edit `
method to allow the user to set it.
.. code-block:: js
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
:doc:`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
:doc:`cancel_edit `
method and inform the user of the results.
.. code-block:: py
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
=============================
How to add a button to a form
=============================
The simplest way to add a button to an edit / view from is to use
:doc:`add_edit_button ` /
:doc:`add_view_button `
correspondingly. You can call this functions in the
:doc:`on_edit_form_created ` /
:doc:`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:
.. code-block:: js
function on_view_form_created(item) {
item.table_options.multiselect = false;
if (!item.lookup_field) {
var print_btn = item.add_view_button('Print', {image: 'bi bi-printer'}),
email_btn = item.add_view_button('Send email', {image: 'bi bi-mailbox'});
email_btn.click(function() { send_email() });
print_btn.click(function() { print(item) });
item.table_options.multiselect = true;
}
}
In this code the item's
:doc:`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.
================================================
How can I perform calculations in the background
================================================
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:
.. code-block:: py
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.
.. note::
The Jam.py V7 introduced the calculated field. It is now possible to use the server side functions (SUM, COUNT, MIN, MAX, AVG), for the lookup to some other table field in a Master/Detail scenario. The Users might review the server side calculations code and replace it with a calculated fields, if appropriate.
===============================================
How to cascade delete records?
===============================================
This example is using Demo ``Albums`` and ``Artists`` tables.
For any ``Albums`` record, it is not possible to delete the record if
the lookup ``Artist`` record exists in the ``Artists`` table. And vice versa.
However, if :doc:`Soft Delete ` is used,
we can use the below **Server Module** code to change the :doc:`Deleted flag `
to ``True``.
For tables with the "Foreign Key" constraint, only the ``Soft Delete`` is needed to achieve the same result.
The records will not be physically deleted in this case.
For table with no "Foreign Key" constraint and no ``Soft Delete``, the records will be deleted permanently.
The below code is within the ``Albums`` **Server Module**:
.. code-block:: py
def cascade_artist_children(delta=None, connection=None):
artists = task.artists.copy(handlers=False)
artists.set_where(id=delta.artist.value)
artists.open()
for i in range(artists.record_count()):
artists.rec_no = i
artists.delete()
artists.apply()
print("Delete applied successfully!")
def on_before_apply_record(item, delta, params, connection):
if delta and hasattr(delta, 'rec_deleted') and delta.rec_deleted():
cascade_artist_children(delta, connection)
delta._lookup_refs = {}
Similar code for ``Artists`` table:
.. code-block:: py
def cascade_albums_children(delta=None, connection=None):
albums = task.albums.copy(handlers=False)
albums.set_where(artist=delta.id.value)
albums.open()
for i in range(albums.record_count()):
albums.rec_no = i
albums.delete()
albums.apply()
print("Delete applied successfully!")
def on_before_apply_record(item, delta, params, connection):
if delta and hasattr(delta, 'rec_deleted') and delta.rec_deleted():
cascade_albums_children(delta, connection)
delta._lookup_refs = {}
The JS code to alert the user with delete status:
.. code-block:: js
function on_before_delete(item) {
item.alert_success('Successful delete!');
return false;
}
===================================================
How to change style and attributes of form elements
===================================================
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:
.. code-block:: js
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
:doc:`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):
.. code-block:: js
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
:doc:`create_inputs `
after the
:doc:`on_edit_form_created `
event have been triggered (see the ``on_edit_form_created`` event handler in the
task client module) you must write
:doc:`on_edit_form_shown `
event handler to change inputs.
For example this code
.. code-block:: js
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:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/form_elements_style_jampy.png
:align: center
:alt: form_elements_style_jampy.png
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:
.. code-block:: js
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:
.. code-block:: css
.view-form .form-footer .btn i {
display: none;
}
More edit form examples:
.. code-block:: css
.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
:doc:`item_name `
attribute of the item.
The following code will remove images in the buttons only in the **Invoices**
view form:
.. code-block:: css
.view-form.invoices .form-footer .btn i {
display: none;
}
You can change the way tables are displayed. The tables that are created by the
:doc:`create_table `
method have a css class "dbtable" and a class with a name that is the
:doc:`item_name `
attribute of the item. Each column of the table also has a class with a
name that is the
:doc:`field_name `
attribute of the corresponding field.
The example, the following code will display cells of the **Invoices** table
**Customer** column bold:
.. code-block:: css
.dbtable.invoices td.customer {
font-weight: bold;
}
One more way to change the way the field column is displayed is to write the
:doc:`on_field_get_html `
event handler.
For example:
.. code-block:: js
function on_field_get_html(field) {
if (field.field_name === 'total') {
if (field.value > 10) {
return '' + field.display_text + '';
}
}
}
===========================
How to create a custom menu
===========================
To create a custom menu you must specify a custom_menu option for the task's
:doc:`create_menu `
method in the task's client module.
====================================================
How to Navigate Between Forms While Preserving Data
====================================================
This guide explains how to navigate from one form to another while maintaining data integrity across forms,
when creating a new record with :doc:`modal ` form.
The pattern involves:
A View with a button that opens an empty Form (Form 2).
Form 2 with a navigation button to Form 1.
Form 1 that receives data from Form 2, saves record, or go back optionally:
.. code-block:: text
DB record
↑
Form 2 Form 1
↓ ↑
└── loop ─┘
1. Set Up the Initial View
========================================
Add a **New** button to your View that opens Form 2:
.. code:: js
// ===== VIEW CONFIGURATION =====
function on_view_form_created(item) {
item.view_form.find('#new-btn')
.text('New')
.off('click.task')
.on('click', function() {
openForm2();
});
item.refresh_page(true);
}
function openForm2() {
task.f2.open({open_empty: true});
task.f2.append_record();
}
2. Configure Form 2 (Intermediate Form)
========================================
Set up Form 2 with a **Next Form** button that navigates back to Form 1:
.. code:: js
// ===== FORM 2 CONFIGURATION =====
function on_edit_form_created(item) {
item.edit_form.find('#ok-btn')
.text('Next Form')
.off('click.task')
.on('click', function() {
item.close_edit_form();
setTimeout(function() {
openForm1(item);
}, 300);
});
}
function openForm1(item) {
task.f1.open({open_empty: true});
task.f1.append_record();
}
function on_edit_form_close_query(item) {
return true;
}
3. Configure Form 1 (Destination Form)
========================================
Map data from Form 2 to Form 1 fields:
.. code:: js
// ===== FORM 1 CONFIGURATION =====
function on_edit_form_created(item) {
var title = 'First Form value: ';
if (item.is_new()) {
// Transfer data from Form 2 to Form 1
item.f1t1.value = task.f2.f2t1.value;
if (item.f1t1.value) {
title += item.f1t1.value + ' value typed';
}
item.edit_options.title = title;
} else {
title = item.f1t1.value;
item.edit_options.title = title;
}
}
4. Back to Intermediate Form
========================================
The **Back** button can be implemented in a similar way:
.. code:: js
// ===== FORM 1 CONFIGURATION =====
function on_edit_form_created(item) {
var title = 'First Form value: ';
if (item.is_new()) {
// Transfer data from Form 2 to Form 1
item.f1t1.value = task.f2.f2t1.value;
if (item.f1t1.value) {
title += item.f1t1.value + ' value typed';
}
item.edit_options.title = title;
} else {
title = item.f1t1.value;
item.edit_options.title = title;
}
item.edit_form.find('#cancel-btn')
.text('Back')
.off('click.task')
.on('click', function() {
item.close_edit_form();
setTimeout(function() {
goBackToForm2();
}, 300);
});
}
function goBackToForm2() {
task.f2.open({open_empty: true});
task.f2.append_record();
task.f2.f2t1.value = task.f1.f1t1.value;
}
function on_edit_form_close_query(item) {
return true;
}
Key Points to Remember
========================================
``open_empty:`` true: Ensures forms open without pre-loaded data
``append_record():`` Adds a new empty record to the form
``setTimeout():`` Allows proper form closure before opening the next form
``on_edit_form_close_query:`` Returns true to bypass unsaved changes warnings
Field Mapping Reference
========================================
+-------------------+-----------------+-----------------------------------------------------------+
| Source |Destination | Description |
+===================+=================+===========================================================+
| task.f2.f2t1.value| item.f1t1.value | Transfers data from Form 2 field f2t1 to Form 1 field f1t1|
+-------------------+-----------------+-----------------------------------------------------------+
See also
========
:doc:`on_edit_form_close_query `
:doc:`Forms `
:doc:`create_edit_form `
:doc:`edit_form `
=====================================================
How can I use data from some other database(s) tables
=====================================================
You can use data from other database tables.
First you must specify table name and fields information.
You can do it the following way:
* Select project node in the task tree and click **Database** button.
* Set DB manual mode and specify the database connection attributes.
* Import tables information as described in the
:doc:`Integration with existing database `
* Select project node in the task tree, click **Database** button and restore previous values.
Then in the ``Server module`` of the new items, add code to read and write
the data to the database tables.
Below is the code for MySQL database (auto incremented primary field):
.. code-block:: py
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):
.. code-block:: py
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:
.. code-block:: py
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
Example for MSSQL:
.. code-block:: py
import pymssql
from jam.db import mssql
def on_open(item, params):
connection = item.task.create_connection_ex(mssql, database='jam7', \
user='sa', password='password', server='127.0.0.1', port='1433')
try:
sql = item.get_select_query(params, mssql)
rows = item.task.select(sql, connection, mssql)
finally:
connection.close()
return rows, ''
def on_apply(item, delta, params):
connection = item.task.create_connection_ex(mssql, database='jam7', \
user='sa', password='password', server='127.0.0.1', port='1433')
try:
sql = delta.apply_sql(params, mssql)
result = item.task.execute(sql, None, connection, mssql)
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.
.. note::
The above procedures were not tested with Jam.py V7.
The simple solution is to use the database synonyms.
===============================================
Is it supported to have details inside details?
===============================================
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:
.. code-block:: js
function on_edit_form_created(item) {
item.edit_options.form_header = false;
var q = task.questions.copy();
q.set_where({pool: item.id.value});
q.view(item.edit_form.find('.edit-detail'));
q.view_options.form_header = false;
q.on_view_form_created = function(quest) {
quest.paginate = false;
quest.view_options.form_header = 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) {
q.pool.value = item.id.value;
};
}
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({id: item.id.value});
q.open();
while (!q.eof()) {
q.delete();
}
q.apply();
}
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/details_jampy.png
:align: center
:alt: details_jampy.png
======================================
How to execute Python code from client
======================================
While it is possible to execute any Python script on the OS level with the
``Popen`` command, first we will demonstrate using the ``Server module[F8]`` code.
You can use
:doc:`server `
method to send a request to the server to execute a function defined in the
server module of an item.
In 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
:doc:`server `
method of the item to run the ``calculate`` function defined in the server module
of the item.
The code in the client module:
.. code-block:: js
function on_view_form_created(item) {
var btn = item.add_view_button('Calculate', {type: 'primary'});
btn.click(function() {
item.server('calculate', [1, 2, 3], function(result, error) {
if (error) {
item.alert_error(error);
}
else {
console.log(result);
}
})
});
}
The code in the server module:
.. code-block:: py
def calculate(item, a, b, c):
return a + b + c
To execute the OS script, we could use the Server module code with ``Popen`` and
a button similar to above:
.. code-block:: py
build = Popen([make, 'html'] , cwd=build_path, stderr=STDOUT,stdout = PIPE, shell=shell)
result, err = build.communicate()
result = result.decode("utf-8")
To review the build result, we can use JavaScript modal form with a button to
display it:
.. code-block:: js
item.edit_form.find("#build-info-btn").hide().click(function() {
show_build_info(item);
});
function show_build_info(item) {
var i = 0,
color,
html = '
',
info = item.build_result.split('\n');
for (i = 0; i < info.length; i++) {
color = '#333333';
if (build_problems(item, info[i])) {
color = 'red';
}
html += '' + info[i] + ' ';
}
html += '
';
html = $(html).css("margin", 20);
task.message(html, {width: 700, height: 600,
title: 'Build information', footer: false, print: true});
}
On this example, the Sphinx ``make`` command is used to build Jam.py Docs.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/how_to_run_python_script_jampy.png
:align: center
:alt: how_to_run_python_script_jampy.png
============================================
How to implement a many-to-many relationship
============================================
Many-to-many relationship is implemented on Demo application with the Invoices table.
Each Customer can have multiple Tracks purchased, and each Track can be sold to
multiple Customers.
No code is needed to achieve this.
What Jam.py is providing is even more. As seen on Tracks form, it is possible to
identify which Customer purchased which Track, with automated summary.
Some code is needed to achieve finding the Customers.
======================
How to link two tables
======================
The below procedure was valid for Jam.py V5, for the scenario when the two
database tables were not directly linked by a Master/Detail relationship within
the Builder (see :doc:`Tutorial. Part 3. Detail `).
In Jam.py V7, the database table Tracks is directly linked to the detail table
invoicetable, hence the below procedure is not needed.
If the tables were not directly linked within the Builder, we could still use
the procedure with Jam.py V7.
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 of
:doc:`view_form `
is defined in the
:doc:`on_view_form_created `
event handler declared in the task client module.
We will change it in the
:doc:`on_view_form_created `
event handler in the tracks client module
.. code-block:: py
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
.. code-block:: py
item.table_options.height -= 200;
create a
:doc:`copy `
of invoice_table, set its
:doc:`paginate ` attribute to false and call the
:doc:`create_table `
method to create a table that will display the sold tracks
.. code-block:: py
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
:doc:`on_after_scroll `
event handler:
.. code-block:: py
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
:doc:`on_after_scroll `
event is triggered whenever the current record is changed.
So when the track is changed we call
:doc:`open `
method, pre-setting the filter and order
.. code-block:: js
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
:doc:`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
:doc:`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:
.. code-block:: js
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
:doc:`detail ` it has the **invoice** 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
:doc:`create_table `
method the function that will be executed
when user double click the record:
.. code-block:: js
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:
.. code-block:: js
function show_invoice(invoice_table) {
var invoices = task.invoices.copy();
invoices.set_where({id: invoice_table.invoice.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
:doc:`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
:doc:`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
:doc:`locate `
method.
Finally we will check the
:doc:`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
:doc:`on_view_form_created `
will be as follows:
.. code-block:: js
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.invoice.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();
});
}
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/two_tables_jampy.png
:align: center
:alt: two_tables_jampy.png
========================================
How to migrate development to production
========================================
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
:doc:`Export/import metadata `.
The process of importing metadata depends on the type of project database.
New project migration
=====================
* Create an empty database in the production environment
* Run *jam-project.py* script to create a new project
* Set up the server. See
* :doc:`Jam.py deployment with Apache and mod_wsgi `,
* :doc:`How to deploy `.
* In the browser start the Application Builder and finish the creation of the
project with an empty database.
* open
:doc:`Parameters ` dialogue to set up the project.
Setup the following parameters:
* **Production** to true
* **Safe mode**
* **Debugging** to false
* Export the metadata of the development project to a zip file
in the Application Builder by clicking the
:doc:`Export ` button.
* Import the metadata to the new project.
.. note::
For projects with **SQLite** database you can simply copy the development
project folder to the production environment.
Existing project migration
==========================
* Export the metadata of the development project to a zip file.
* Import the metadata to the production project.
.. 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.
Importing metadata with the http server process shutdown
========================================================
Stop the http server and copy the metadata zip file to *migration* folder in the
project directory. If the folder doesn't exist, create it.
Start the http 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.
Importing metadata without the http server process shutdown
=============================================================
Click the
:doc:`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
:doc:`Parameters `
to delay the change the database or
use Importing metadata with server shutdown.
==================================
How to migrate to another database
==================================
TBA - changed from Jam.py v5.
You can migrate your data to another database.
For example, you developed your project with SQLite database and 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
:doc:`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
:doc:`copy_database ` method of the task:
* within the Project\Task create the following Server Module function (adjust the below database path with correct one):
.. code-block:: py
from jam.db.db_modules import SQLITE
def copy_db(task):
task.copy_database(SQLITE, '/home/work/demo/demo.sqlite')
* then, execute it with the one of the following ways:
* call this function in the
:doc:`on_created ` event handler:
.. code-block:: py
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
:doc:`server ` method to execute it
.. code-block:: js
function on_view_form_created(item) {
item.add_view_button('Copy DB').click(function() {
task.server('copy_db')
});
}
* or run from from debugging console of the browser:
.. code-block:: js
task.server('copy_db')
#. Remove the code that was used immediately after this procedure.
.. note::
You can not migrate to SQLite database if the current database has foreign
keys.
==================================
How to migrate v5 project to v7
==================================
For any v5 project, backup **index.html** file first and copy **index.html** and
**template.html** from v7 Jam.py ``Demo`` application into the application folder.
Or, copy the same files from a new v7 project created with ``jam-project.py``.
Uninstall the v5 Jam.py and install the v7. Or run two virtual Python instances
with both Jam.py versions.
Start the application as usual with v7 Jam.py version.
Address the following:
#. The biggest obstacle to move a Jam.py v5 application is the "Edit Lock" (record locking), enabled on the table(s). The Jam.py v7 is expecting a `record_version` (INT) field for every table with the Edit Lock.
There are two options when migrating to v7. One is to disable the "Edit Lock" before migration, and the second option is to add the `record_version` field to affected tables before the migration.
After the field is added to the database structure, we need to add it to the Application Builder manually, with "DB Manual Mode" turned ON for every table with "Edit Lock" enabled. The "record version" option should be set with `record_version` field.
Obviously, disabling the "Edit Lock" does not need anything. There is no option on project Parameters to create the "Lock Item".
#. The next issue is templates. The Jam.py v7 is expecting the **templates.html** file within the application folder. Due to Bootstrap 5 usage, some minor differences will arise with moving the existing templates from Jam.py v5 **index.html** file to **templates.html** file.
#. **index.html** also needs changing, due to Boostrap 5 dependencies. Please see below example.
#. Foreign Key support is dropped at v7. This means if there are any Foreign Keys created at v5, there will be no option to manage it at v7.
#. The Jam.py v7 introduced the calculated field. It is now possible to use the server side functions (SUM, COUNT, MIN, MAX, AVG), for the lookup to a table field in a Master/Detail scenario. The Users might review the server side calculations code and replace it with a calculated fields, if appropriate.
#. Jam.py v7 is now utilising the Python dependencies and will automatically install needed libraries with the initial install. This is different to v5, where all dependencies were "locked in" with the Jam.py distribution, enabling a single ``jam`` folder for deployment with the application.
#. For applications with Jam.py version 5.x or below, replace the **Task/Client** module code with **Task/Client** module code from ``Demo`` application or the new v7 project. Than, add a custom code from v5 **Task/Client** module, if there was any.
For example, ``Demo`` application **index.html** file contains:
.. code-block:: html
...
Change to:
.. code-block:: html
...
The rest of **index.html** file change should be minimal.
=================================================================================================
How to implement some sort of basic multi-tenancy? For example, to have users with separate data.
=================================================================================================
You can implement a multi-tenancy using Jam.py.
For example, if some item has a user_id field (type INT), the following code in the
server module of the item will do the job. The authentication must be enabled:
.. code-block:: py
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
:doc:`session `
attribute of the item to get a unique user id
and
:doc:`on_open ` and
:doc:`on_apply `
event handlers.
The
:doc:`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
:doc:`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 has
a user_id field:
.. code-block:: py
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.')
The user might combine the above with the :doc:`authentication `.
=================================================
How to prevent duplicate values in a table field
=================================================
One of the ways to do it is to write the
:doc:`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.
.. code-block:: py
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)
===============================
How to prohibit changing record
===============================
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
:doc:`on_after_scroll `
event handler and using
:doc:`permissions ` property:
.. code-block:: js
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
:doc:`permissions ` property attributes to true.
We can also write the
:doc:`on_apply `
event handler in the server module of the item:
.. code-block:: py
def on_apply(item, delta, params, connection):
for d in delta:
if d.posted.old_value:
raise Exception('Document posted. No change allowed')
==============================================================================
How I can process a request or get some data from other application or service
==============================================================================
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:
.. code-block:: html
http://your_jampy_app.com/ext/something
When an web app on the server receives such request, it
generates the
:doc:`on_ext_request `
event
For example, Jam.py application table ``account_transactions`` has a field ``actual_amount``.
The application Task Module has:
.. code-block:: py
def on_ext_request(task, request, params):
reqs = request.split('/')
if reqs[2] == 'expenses':
result = task.account_transactions.expenses(task, params)
return result
The table ``account_transactions`` Task Server Module has:
.. code-block:: py
from jam.common import cur_to_str
def expenses(item, params):
inv = item.task.account_transactions.copy()
inv.open()
total = 0
for i in inv:
total += i.actual_amount.value
total = cur_to_str(total)
return(total)
Accessing the application with Curl command will reply with the result::
...\> curl -k https://your_jampy_app.com/ext/expenses -d "[]" -H "Content-Type: application/json"
{"result": {"status": 9, "data": "-$2590.01", "modification": 99}, "error": null}
Using variables with Curl
=========================
On Demo application, if we add to Task Server Module:
.. code-block:: py
def on_ext_request(task, request, params):
print(request, params)
reqs = request.split('/')
if reqs[2] == 'bla':
users = task.customers.copy(handlers=False)
users.set_where(id=params['id'])
users.open()
if users.rec_count == 1:
return {
'id': users.firstname.value,
'firstname': users.firstname.value,
}
Passing parameters with Curl will reply with the result::
...\> curl http://localhost:8080/ext/bla -d '{"id": "2", "firstname": "Leonie"}' -H "Content-Type: application/json"
{"result": {"status": 9, "data": {"id": "Leonie", "firstname": "Leonie"}, "modification": 2014}, "error": null}
Consuming data from the request
===============================
The same application from above can be accessed from some other Jam.py app with
Server Module:
.. code-block:: py
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
params = []
def api_fetch(url, request, params):
try:
a = urlopen(url + '/' + request, data=str.encode(json.dumps(params)))
r = json.loads(a.read().decode())
return r['result']['data']
except:
return False
def send(item):
result= ''
res = []
request = 'expenses';
endpoint = 'https://your_jampy_app.com/ext';
try:
# print('Req: ' + request)
result = api_fetch(endpoint, request, [])
except:
return False
if result:
# print(result)
res.append(
{
# 'id': 1,
'request': request,
'endpoint': endpoint,
'value': result,
}
)
return res
else:
raise Exception('Could not connect!')
Client Module for some virtual table with fields ``request``, ``endpoint``, and ``value``:
.. code-block:: js
function on_view_form_created(item) {
item.view_options.open_item = false;
item.view_options.form_header = false;
item.open({open_empty: true});
item.paginate = false;
item.view_form.find("#edit-btn").hide();
item.view_form.find("#delete-btn").hide();
item.view_form.find("#new-btn").hide();
item.alert('Fetching!');
item.server('send', function(records, err) {
item.disable_controls();
if (err) {
item.warning('Failed to fetch data: ' + err);
}
else {
if (records.length > 0) {
records.forEach(function(rec) {
item.append();
// item.id.value = rec.id;
item.request.value = rec.request;
item.endpoint.value = rec.endpoint;
item.value.value = rec.value;
item.post();
});
item.first();
item.enable_controls();
item.alert('Successfully fetched from API!');
}
}
});
}
The result will be displayed table with fetched ``value`` from ``endpoint``
with the ``request``.
====================================================================
How to save changes to two tables in same transaction on the server
====================================================================
Below are two examples.
In the first example each
:doc:`apply `
method gets its own connection from connection pool and commits it after saving
changes to the database.
In the second example the connection is received from connection pool and passed
to each
:doc:`apply `
method so changes are committed at the end.
.. code-block:: py
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()
.. code-block:: py
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()
========================================
How to save edit form without closing it
========================================
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.
.. code-block:: js
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();
}
});
}
.. code-block:: js
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();
});
}
});
}
=======================================
Can I use Jam.py with existing database
=======================================
Please read this:
:doc:`Integration with existing database `
===========================
How to validate field value
===========================
Write the
:doc:`on_field_validate `
event handler to validate field value.
For example, The event will triggered when the
:doc:`post `
method is called, that saves the record in memory or
when the user leaves the input used to edit the unitprice field value.
.. code-block:: js
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
:doc:`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:
.. code-block:: js
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
:doc:`on_edit_form_shown `
event handler we iterate through all the fields using the
:doc:`each_field `
method and find the input for each field, if it exists. Each input has a class
with the name of the field (:doc:`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
:doc:`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
:doc:`on_edit_form_shown ` of the task
is called first for every item when edit form is shown. See
:doc:`Form events `.
.. code-block:: js
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.
===========================
How to write tests
===========================
Jam.py is using Mocha/Chai for front-end unit tests and pytest for dataset integration testing.
The examples are in tests folder.
First, start with forking `Jam.py-v7`_.
.. _Jam.py-v7: https://github.com/jam-py-v5/jam-py-v7
Next, clone your fork::
...\> git clone https://github.com/YourGitHubName/jam-py-v7.git
...\> cd jam-py-v7/tests/project
To add a new test for the front-end, add JavaScript file into project/js folder.
Let's say we want to test ``user`` table with CRUD, using one field called ``username``.
The project folder has the below structure::
├── admin.sqlite
├── css
│ └── project.css
├── index.html
├── js
│ ├── test_dataset.js
│ ├── test_details.js
│ ├── test_edit_lock.js
│ ├── test_fields.js
│ ├── test.js
│ └── test_locale.js
├── langs.sqlite
├── server.py
├── templates.html
├── test.html
├── test.sqlite
└── wsgi.py
Start the project as usual::
...\> ./server.py
Add table ``Users`` with a field name ``Username`` on builder, ie. visiting:
.. code-block:: html
127.0.0.1:8080/builder.html
Add to index.html new file:
.. code-block:: html
The index.html might look like below:
.. code-block:: html
Jam.py tests
Create the file **js/test_users.js** with the tests and visit the application on:
.. code-block:: html
127.0.0.1:8080/index.html
All unit tests will run and display the results. The database tests.sqlite will be updated with a
new user. The ``DELETED`` field within the table will be set to 1 for new row, if the table was
created with ``Soft delete`` option. If not, the new record will be deleted.
If all good to go, create a Github ``pull`` request with the changes.
======================================================================
How to append a record using an edit form without opening a view form?
======================================================================
You must first call the
:doc:`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:
.. code-block:: js
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
:doc:`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.
.. code-block:: js
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 executed synchronously.
The code below does it asynchronously:
.. code-block:: js
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
:doc:`edit_options `
to make the edit form modal:
.. code-block:: js
var invoices = task.invoices.copy();
invoices.edit_options.modeless = false;
============================
Business application builder
============================
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
.. code-block:: console
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.
.. toctree::
:maxdepth: 1
project/index
roles
users
code_editor
task
groups/index
items/index
details
lookup_lists
integration_with_existing_database
saving_history
record_locking
language_support
language_translation
sanitizing
accept_string
routing
customisation
==================
Project management
==================
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:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/project_node_jampy.png
:scale: 50%
:align: center
:alt: Project management
.. important::
As seen on top right corner, The Name of the application is displayed, name of the database used, application version number, and
``Jam.py`` framework ``version`` number.
Click on the links below to see the purpose of the buttons in the right panel of
the page.
.. toctree::
:maxdepth: 1
parameters
database
export
import
metadata_file
find
print
==========
Parameters
==========
After clicking on the **Parameters** button the Parameters Dialog will appear.
It has two tabs **General** and **Interface**.
General tab
===========
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/parameters_jampy.png
:align: center
:alt: Project parameters dialog
On the General tab, you can specify general parameters of the project:
* **Production** - if this checkbox is checked, the access to Application Builder is disabled.
To enable the access, the ``builder.html`` file should be deleted from the application folder.
* **Safe mode** - if safe mode is enabled, authentication is needed for user to
work in the system (See
:doc:`Users `
and
:doc:`Roles `
).
* **Debugging** - if this checkbox is checked, the Werkzeug library debugger will
be invoked when an error on the server occurs.
* **Show SELECT SQL** - if this checkbox is checked, the SELECT SQL will be displayed.
* **Language** - use it to open Language Dialog. See
:doc:`Language support `
* **Persistent connection** - if this checkbox is checked the application creates
a connection pool otherwise a connection is created before executing the sql query.
* **Connection pool size** — the size of the server database connection pool.
* **Compressed JS, CSS files** - If this button is checked the server returns
compressed *js* and *css* files when *index.html* page is loaded.
* **All JS modules in a single file** - If this checkbox is unchecked, the
application will generate a javascript file in the project *js* folder for
every item in the :doc:`task tree `, that has code in
its Client module, with the name *item_name.js*, where *item_name* is the name
of an item.
Otherwise, the application will generate a javascript file with the name
*task_name.js*, where *task_name* is the name of the project
:doc:`task ` (for example *demo.js*), that will contain javascript
code of all items, except items, whose **External js module** checkbox in the
:doc:`Item Editor Dialog `
is checked (separate files will be created for them).
* **Dynamic JS modules loading** - If this checkbox is unchecked and the
application generates more than one javascript file, only file named
*task_name.js* will be loaded when application is run. All other files must be
loaded dynamically. See :doc:`Working with modules `.
* **History item** - to specify item, that will store change history, see
:doc:`Saving audit trail/change history made by users `
* **Session timeout (seconds)** - number of seconds of inactivity that is allowed
before the session expires.
* **Session ignore change ip** - if false, the session is only valid when
it is accessed from the same ip address that created the session.
* **Max content length (MB)** - use it to limit the total content length of the
request to the server, in megabytes.
* **Import delay (seconds)** - if set the application will wait the number of
seconds set in the parameter before changing the project dataset while
:doc:`importing project metadata `
,
otherwise it waits for 5 minutes or until all previous request
to the server in the current process will be processed.
* **Delete reports after (hours)** - if a value is specified the generated
reports that are located in the static/reports folder will be deleted after
specified number of hours have passed.
* **Upload file extensions** - is an :doc:`Accept string `
that defines the types of files that could be uploaded to the server by the
task :doc:`upload ` method. Uploading files that
do not match these types is prohibited.
* **Version** — specify the version of the project here.
.. note::
When **Connection pool size** or **Persistent connection** parameters are
changed, the server application must be restarted for changes to take effect.
Interface tab
=============
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/parameters_interface_jampy.png
:align: center
:alt: parameters_interface_jampy.png
On the Interface tab, you can specify interface parameters of the project:
* **Theme** - use this parameter to select the theme of the project from one of
predefined themes
* **Docs Link** - use this parameter to specify the Docs location (TBA)
* **Small font** - if this button is checked, the default font size will be 12px,
otherwise it is 14px
* **Full width** - if this button is checked the project will fill the page width,
without left and right margins
* **Display forms in tabs** - if this button is checked, the forms will be opened
tabs
========
Database
========
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/database_jampy.png
:scale: 50%
:align: center
:alt: Database dialog
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 the **DB Manual Update** checkbox is unchecked (default), any changes made to an
item that has an associated database table will automatically be applied to that table.
For example, if you add a new field in the :doc:`Item Editor Dialog `,
the corresponding
column will be created in the associated database table.
If the checkbox is checked, the system will not perform any automatic modifications
to the database tables. In this case, all database changes must be applied manually.
The **DB manual update** was renamed to **DB manual mode** in more recent version.
.. note::
Please be very careful when using this option.
Examples of database setups
===========================
.. admonition:: Adapted from `Jam.py Design Tips`_
Jam.py supports many different database servers. For example PostgreSQL_, MariaDB_, MySQL_, MSSQL_, Oracle_, Firebird_, IBM_, SQLite_, DuckDB_, and SQLite with SQLCipher_.
If you are developing a small project or something you don't plan to deploy in
a production environment, SQLite is generally the best option as it doesn't
require running a separate server. However, SQLite has many differences from
other databases, so if you are working on something substantial, it's
recommended to develop with the same database that you plan on using in
production.
In addition to a database backend, we need to make sure the Python
database bindings are installed.
* If using PostgreSQL_, the ``psycopg2`` or ``psycopg2-binary`` package is needed.
* If using MySQL or MariaDB_, the ``MySQLdb`` for Python 2.x is needed. For Python 3.x, the ``mysql-connector-python`` and ``mysqlclient`` package is needed, as well as database client development files.
* If using MSSQL_, the ``pymssql`` is needed. For ODBC on MS Windows, the ``pyodbc`` is needed. Configure the ODBC as usual with the DSN as a content.
* If using Oracle, the `cx_Oracle`_ is needed, as well as Python headers (development files).
* If using SQLCipher_, ``sqlcipher3-binary`` package is needed for Linux. There is a standalone DLL for Windows available.
* If using IBM_ (TBA), ``ibm_db`` and ``ibm_db_dbi`` package is needed.
* If using Firebird_, ``fdb`` package is needed.
* If using DuckDB_, ``duckdb`` package is needed.
* If using Databricks_, ``databricks-sql-connector`` is needed.
.. _Jam.py Design Tips: https://jampy-application-design-tips.readthedocs.io/
.. _PostgreSQL: https://www.postgresql.org/
.. _MariaDB: https://mariadb.org/
.. _MySQL: https://www.mysql.com/
.. _psycopg2: https://www.psycopg.org/
.. _SQLite: https://www.sqlite.org/
.. _cx_Oracle: https://oracle.github.io/python-cx_Oracle/
.. _Oracle: https://www.oracle.com/
.. _MSSQL: https://www.microsoft.com/en-au/sql-server/sql-server-downloads
.. _Firebird: https://firebirdsql.org/
.. _SQLCipher: https://github.com/sqlcipher
.. _IBM: https://www.ibm.com/support/pages/downloading-ibm-db2-version-115-linux-unix-and-windows
.. _Databricks: https://www.databricks.com/blog/how-use-lakebase-transactional-data-layer-databricks-apps
.. _DuckDB: https://github.com/duckdb/duckdb
.. note::
For **SQLite** databases, certain schema changes - such as deleting or renaming
a field, or creating a foreign key - require the
:doc:`Application Builder `
to recreate the table. In this process, a new table is created and all records
are copied from the original table into it.
Additionally, Jam.py does not support importing metadata into an existing SQLite
project (i.e., a project with already created tables).
Metadata can only be imported when creating a new project.
Using MySQL on Windows is supported, please visit `MySQL deployment on Windows`_.
Even though Jam.py supports all databases from the above, there is no guarantee
that some specific and/or propriety database functionality is supported.
Here we name a few tested databases:
.. _MySQL deployment on Windows: https://www.radishlogic.com/coding/python-3/installing-mysqldb-for-python-3-in-windows/
SQLite
------
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/SQLite_setup_jampy.png
:scale: 50%
:align: center
:alt: SQLite setup
PostgreSQL
----------
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/postgres_setup_jampy.png
:scale: 50%
:align: center
:alt: PostgreSQL setup
MySql
-----
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/MySql_setup_jampy.png
:scale: 50%
:align: center
:alt: MySql setup
FireBird
--------
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/firebird_setup_jampy.png
:scale: 50%
:align: center
:alt: FireBird setup
MSSQL
-----
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/mssql_setup_jampy.png
:scale: 50%
:align: center
:alt: MSSQL setup
.. note::
DSN example: ``DRIVER={SQL Server}; SERVER=localhost\MSSQLSERVER01; DATABASE=master; Trusted_Connection=yes;``
Oracle
------
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/oracle_setup_jampy.png
:scale: 50%
:align: center
:alt: Oracle setup
Databricks
----------
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/databricks_connection_jampy.png
:scale: 50%
:align: center
:alt: Databricks setup
.. note::
Databricks workspace and database schema name is typed as ``workspace.database``
in the ``Database`` field. This is visible on the screenshot as
``samples.bakehouse`` in the upper right corner.
Not all DDL is supported.
======
Export
======
Press this button to export project
:doc:`metadata `
to zip file.
See also
========
:doc:`Import `
:doc:`Metadata file `
:doc:`How to migrate development to production `
====
Find
====
Press this button to to search for the character string in all modules of
the project.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/find_jampy.png
:align: center
:alt: Find in project dialog
======
Import
======
Use this button to import project
:doc:`metadata `
from zip file.
See also
========
:doc:`Export `
:doc:`Metadata file `
:doc:`How to migrate development to production `
======================
Export/import metadata
======================
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.
Export metadata
===============
The project metadata can be exported to a zip file in the Application Builder
by clicking the :doc:`Export ` button.
This file contains the following information:
* Information about project data structure (information about DB tables, fields,
indexes defined in the project items), code and settings that is stored in
the *admin.sqlite* database.
* Files from the following folders:
* css
* js
* static/css
* static/js
* static/img
* reports (*ods* files of project report templates)
* utils (this folder may contain python libraries or files used in the project)
Import metadata
===============
The metadata file can be imported to another project.
The web application while importing the metadata performs the following operations:
#. sets the under_maintenance flag so that incoming requests are not processed
by the application. When receiving the response to theses requests the client
application shows the message "Web site currently under maintenance".
Web applications that run in parallel processes upon
receiving requests check whether the under_maintenance flag is set and,
if so, also do not process requests.
#. unzips the metadata file to the temporary folder in the project directory
#. checks the data integrity - items with the same
:doc:`ID ` in the current project and imported
metadata must have the same type and the same
:doc:`table_name `
attribute if one is specified for an item
#. analyzes the metadata to generate sql queries to update the project
database structure and records of the *admin.sqlite* database.
#. if the **Import delay** attribute in the project
:doc:`Parameters `
is set, waits the number of seconds set in the attribute,
otherwise waits for 5 minutes or until all previous request to the server
will be processed.
#. updates the project database and modifies the records of the *admin.sqlite*,
see bellow_
#. copies files from the temporary folder to the project folder
#. reloads the task tree
#. writes the import log file to the logs folder and sends the import log to
the Application builder to be displayed
#. deletes the temporary folder
#. removes the under_maintenance flag
#. increases the build flag, so web applications that run in parallel
processes and monitor this flag reload their task tree
.. _bellow:
Updating project databases
------------------------------
The way the the project database is updated depends on the type of the
project database.
* Updating databases that support DDL statement rollback (**Postgress**, **Firebird**, **MSSQL**)
* creates the connection to the project database
* starts to execute sql statements to update the project database.
* if an error occurs while updating the project database, rollbacks the changes,
and goes to the step 9.
* after the project database has been updated, makes a copy of the *admin.sqlite*
file and starts modifying the *admin.sqlite* database
* if an error occurs while modifying the records of *admin.sqlite*, restores
*admin.sqlite* from the copy, rollbacks the changes to the project database
and goes to the step 9.
* commits the changes, deletes the copy of the *admin.sqlite*, closes
connection and goes to the step 7.
* Updating databases that do not support DDL statement rollback (**MySql**, **Oracle**)
* creates the connection to the project database
* starts to execute sql statements to update the project database.
* if an error occurs while updating the project database writes error
to the import log and continues until all statements will be processed
* commits the changes
* starts modifying the *admin.sqlite* database
* closes connection and goes to the step 7.
Causes of errors
----------------
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 :doc:`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.
=====
Print
=====
Press this button to print all modules of the project.
=====
Roles
=====
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).
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/roles_jampy.png
:align: center
:alt: Jam.py roles
=====
Users
=====
If the **Safe mode** checkbox in the
:doc:`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:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/users_new_jampy.png
:align: center
:alt: New users dialog
* **Name** – user name
* **Login** - login
* **Password** - password
* **Role** – user role
* **Information** - some additional information
* **Admin** - if this flag is set, the user has the right to work in the Application builder.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/users_jampy.png
:align: center
:alt: Jam.py Application builder Users
See also
========
:doc:`on_login event `
===========
Code editor
===========
For every item of the project :doc:`task tree ` there
are two buttons in the upper-right corner of the
:doc:`Application builder `
: ``Client module [F7]``
and ``Server module[F8]``.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/item_btns_jampy.png
:align: center
:alt: Invoke code editor buttons
By clicking on these buttons the Code Editor for the client or server
module of the item will be opened.
(See :doc:`Working with modules `)
To the left of the **Editor** there is an information pane with four tabs:
* **Module** - this tab displays all events and functions defined in the editor,
double-click on one of them to move the cursor to the proper function.
* **Events** - displays all the published event of the item, double-click to
add a wrapper for the event at the current cursor position (see the
on_before_post event on the figure above).
* **Task** - the
:doc:`task tree `,
double-click on the node to enter the item_name at the current cursor position.
* **Fields** - the field list of the current item, double-click on one of the
fields to enter the field_name at the current cursor position.
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
:doc:`Find in project Dialog `
Jam.py uses the `ace editor`_ editor to implement its code editor. More recent Jam.py
version will use Microsoft `Monaco editor`_.
`Here are keyboard shortcuts for the ace editor`_.
.. _ace editor: https://ace.c9.io
.. _Monaco editor: https://microsoft.github.io/monaco-editor
.. _Here are Keyboard Shortcuts for the ace editor: https://github.com/ajaxorg/ace/wiki/Default-Keyboard-Shortcuts
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/editor_jampy.png
:align: center
:alt: code editor
====
Task
====
Select **Task** node to get to the root of the project
:doc:`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
* ``Client Module [F7]`` and ``Server Module [F8]`` of the task, see :doc:`Working with modules ` and
:doc:`Code editor `
* ``index.html [F10]`` file from the project root folder that contains project page
* ``templates [F9]`` html file for the forms, see :doc:`Forms ` and :doc:`Code editor `
* ``project.css [F11]`` file from **css** directory the project root folder, see
:doc:`Code editor `
* ``Lookup lists`` - click on the button to open
:doc:`Lookup lists ` Dialog
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/task_jampy.png
:align: center
:alt: Task node
======
Groups
======
Select the node with the name of the task to get to the groups of the project
:doc:`task tree `.
At the bottom of the page there are 3 buttons:
* **Delete** - click the button to delete an empty group.
* **Edit** - click this button to modify the selected group, the
corresponding Group Editor will appear.
* **New** - use this button to create a new item.
* **New Report Group** - use this button to create a new report group.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/group_type_jampy.png
:align: center
:alt: Group type
For each of the group, its own editor will be shown:
.. toctree::
:maxdepth: 1
item_group_editor
report_group_editor
table_group_editor
Use buttons in the right panel of the page to edit Client and Server modules of
a selected group, see
* :doc:`Working with modules `,
* :doc:`Code editor `
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/groups_jampy.png
:align: center
:alt: Group node
=================
Item Group Editor
=================
**Item Group Editor** opens when a developer wants to create a new item group or
modify an existing one. See
:doc:`Task tree `
The upper part of the **Item Group Editor** have the following fields:
* **Caption** - the item name that appears to users.
* **Name** - the name of the item that will be used in programming code to
get access to the item object. It should be unique in the project and should
be a valid python identifier.
* **Primary key field** - by clicking on the button to the right of this
attribute you can specify the primary key field for the item. If the primary
key field was defined for the group that owns the item it will be displayed
there by default, otherwise you have to create this field first.
* **Deleted flag field** - by clicking on the button to the right of this
attribute you can specify the field that will serve as a deleted flag
for the item. If the deleted flag field was defined for the group that owns
the item it will be displayed there by default, otherwise you have to create
this field first.
* **Record version** - by clicking on the button to the right of this attribute
you can specify the field that will serve as :doc:`record locking `
field for the item.
* **Visible** - use this checkbox to set item's visible attribute. The value of
this attribute can be used in code on the client to create menu items and so
on.
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
:doc:`common `
to all items the group will own.
To add, modify or delete a field use the following buttons:
* **New** - click the button to invoke the
:doc:`Field Editor Dialog `
to create a new field.
* **Edit** - click the button to invoke the
:doc:`Field Editor Dialog `
to modify a selected field.
* **Delete** - click the button to delete a field selected in in the field list.
In the bottom-right corner of the Dialog form there are two buttons:
* **OK** - click the button to save change you made.
* **Cancel** - click the buttons to cancel the operation.
.. 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.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/item_group_jampy.png
:align: center
:alt: Item Group Editor
===================
Report Group Editor
===================
**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:
* **Caption** - the group name that appears to users.
* **Name** - the name of the group that will be used in programming code to
get access to the group object. It should be unique in the project and should
be a valid python identifier.
* **Visible** - use this checkbox to set group's visible attribute. The value of
this attribute can be used in code on the client to create menu items and so
on.
In the bottom-right corner of the Dialog form there are two buttons:
* **OK** - click the button to save change you made.
* **Cancel** - click the buttons to cancel the operation.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/report_group_jampy.png
:align: center
:alt: Item Group Editor
===================
Detail Group Editor
===================
**Detail Group Editor** opens when a developer wants to create a new detail group
or modify an existing one. See
:doc:`Task tree `
The upper part of the **Detail Group Editor** have the following fields:
* **Caption** - the item name that appears to users.
* **Name** - the name of the item that will be used in programming code to
get access to the item object. It should be unique in the project and should
be a valid python identifier.
* **Table** - the name of the table that will be created in the project
database. This name is specified when creating an item, and can not be
changed later.
* **Primary key field** - by clicking on the button to the right of this
attribute you can specify the primary key field for the item. If the primary
key field was defined for the group that owns the item it will be displayed
there by default, otherwise you have to create this field first.
* **Deleted flag field** - by clicking on the button to the right of this
attribute you can specify the field that will serve as a deleted flag
for the item. If the deleted flag field was defined for the group that owns
the item it will be displayed there by default, otherwise you have to create
this field first.
* **Record version** - by clicking on the button to the right of this attribute
you can specify the field that will serve as :doc:`record locking `
field for the item.
* **Visible** - use this checkbox to set item's visible attribute. The value of
this attribute can be used in code on the client to create menu items and so
on.
* **Soft delete** - when this check-box is checked, the delete method does not
erase a record physically from the table, but uses this field to mark the
record as deleted. See
:doc:`Common fields `,
:doc:`delete `
method (server),
:doc:`delete `
method (client).
* **Virtual table** - if this checkbox is checked, no database table will be
created. Use this options to create an item with in-memory dataset or to use
its modules to write code.
This checkbox must be set when creating an item and can not be changed
later.
* **History** - if this checkbox is checked, the application will saving for this
item audit trail/change history made by users, see
:doc:`Saving audit trail/change history made by users `
* **Edit lock** - if this checkbox is checked, the application will use record
locking while users concurrently edit a record, see
:doc:`Record locking `
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
:doc:`common `
to all items the group will own.
To add, modify or delete a field use the following buttons:
* **New** - click this button to invoke the
:doc:`Field Editor Dialog `
to create a new field.
* **Edit** - click this button to invoke the
:doc:`Field Editor Dialog `
to modify a selected field.
* **Delete** - click the button to delete a field selected in in the field list.
In the bottom-right corner of the Dialog form there are two buttons:
* **OK** - click the button to save change you made.
* **Cancel** - click the buttons to cancel the operation.
.. 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 detail groups, that already own items you can only change
**Caption**, **Name** and **Visible** attributes.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/detail_group_jampy.png
:align: center
:alt: Detail Group Editor
=====
Items
=====
Select a group node in the project tree to get access to items that this
group owns, see
:doc:`Task tree `.
At the bottom of the page there are 3 buttons:
* **New** - click on New to create a new item in the
:doc:`Item Editor Dialog `
* **Edit** - use this button to modify item's attributes as well to add, change
or delete fields in the
:doc:`Item Editor Dialog `
* **Delete** - click on the button to delete an item and its underlying database
table.
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:
* **Client module** - click on this button to open the
:doc:`Code editor `
to edit client module of an item, see
:doc:`Working with modules `.
* **Server module** - click on this button to open the
:doc:`Code editor `
to edit server module of an item, see
:doc:`Working with modules `.
* **View Form** - use this button to invoke the
:doc:`View Form Dialog `
to set how the view form will be displayed.
* **Edit Form** - use this button to invoke the
:doc:`Edit Form Dialog `
to set how the edit form will be displayed.
* **Filters** - use this button to invoke the
:doc:`Filters Dialog `
to create, modify and delete item filters. See
:doc:`Filters `.
* **Details** - use this button to invoke the
:doc:`Details Dialog `
to add or remove details linked to the item.
* **Order** - use this button to invoke the
:doc:`Order Dialog `
to specify how records will be ordered by default. See
:doc:`open `
method
* **Indices** - сlick this button to open the
:doc:`Indices Dialog `
to create and delete indices for the item database table.
..
* **Foreign keys** - сlick this button to open the
:doc:`Foreign Keys Dialog `
to create foreign keys for the database table.
* **Reports** - сlick this button to open the
:doc:`Reports Dialog `
to specify reports that could printed for the item.
A new project has a function that can be used to create a drop-up
button to print the reports.
* **Privileges** - click this button to open a dialog to configure the
privileges assigned to user roles for this item.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/items_jampy.png
:align: center
:alt: Task node
.. toctree::
:maxdepth: 1
item_editor_dialog
field_editor_dialog
edit_form_dialog
view_form_dialog
filters_dialog
details_dialog
order_dialog
indices_dialog
reports_dialog
priviledges_dialog
..
foreign_keys_dialog
==================
Item Editor Dialog
==================
**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
:doc:`Items `.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/item_editor_dialog_jampy.png
:align: center
:alt: Item Editor Dialog
The upper part of the **Item Editor dialog** have the following fields:
* **Caption** - the item name that appears to users.
* **Name** - the name of the item that will be used in programming code to
get access to the item object. It should be unique in the project and should
be a valid python identifier.
* **Table** - the name of the table that will be created in the project
database. This name is specified when creating an item, and can not be
changed later.
* **Primary key field** - by clicking on the button to the right of this
attribute you can specify the primary key field for the item. If the primary
key field was defined for the group that owns the item it will be displayed
there by default, otherwise you have to create this field first.
* **Deleted flag field** - by clicking on the button to the right of this
attribute you can specify the field that will serve as a deleted flag
for the item. If the deleted flag field was defined for the group that owns
the item it will be displayed there by default, otherwise you have to create
this field first.
* **Record version** - by clicking on the button to the right of this attribute
you can specify the field that will serve as :doc:`record locking `
field for the item.
* **Visible** - use this checkbox to set item's visible attribute. The value of
this attribute can be used in code on the client to create menu items and so
on.
* **Soft delete** - when this check-box is checked, the delete method does not
erase a record physically from the table, but uses this field to mark the
record as deleted. See
:doc:`Common fields `,
:doc:`delete `
method (server),
:doc:`delete `
method (client).
* **Virtual table** - if this checkbox is checked, no database table will be
created. Use this options to create an item with in-memory dataset or to use
its modules to write code.
This checkbox must be set when creating an item and can not be changed
later.
* **History** - if this checkbox is checked, the application will saving for this
item audit trail/change history made by users, see
:doc:`Saving audit trail/change history made by users `
* **Edit lock** - if this checkbox is checked, the application will use record
locking while users concurrently edit a record, see
:doc:`Record locking `
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:
* **New** - click this button to invoke the
:doc:`Field Editor Dialog `
to create a new field.
* **Edit** - click this button to invoke the
:doc:`Field Editor Dialog `
to modify a selected field.
* **Delete** - click this button to delete a field selected in in the field list.
In the bottom-right corner of the Dialog form there are two buttons:
* **OK** - click this button to save change you made. If the **Virtual table**
checkbox is not checked and **DB manual update** parameter in the project
:doc:`Database Dialog` is not set, the application
will generate and execute SQL query to update the item table in the project
Database (changes made to the fields will be applied to the table).
* **Cancel** - click this buttons to cancel the operation.
===================
Field Editor Dialog
===================
Use the **Field Editor Dialog** to create a new or modify an existing field.
.. note::
For some operations, the :doc:`DB manual mode ` must be set to true.
For example, changing the field type. Since the database is in "Manual Mode", changing the type
will not reflect within the database structure. Use this with caution.
The dialog has following tabs: **Field**, **Lookup**, **Interface** and **Calculation**.
Field tab
=========
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/fields_editr_field_jampy.png
:align: center
:alt: fields_editr_field_jampy.png
The **Field** tab have the following fields:
* **Caption** - the field name that appears to users.
* **Name** - the name of the field that will be used in programming code to
get access to the field object. It should be a valid python identifier.
* **Type** - type of the field — one of the following values:
* **TEXT**
* **INTEGER**
* **FLOAT**
* **CURRENCY**
* **DATE**
* **DATETIME**
* **BOOLEAN**
* **LONGTEXT**
* **FILE**
* **IMAGE**
* **Size** - the size of the field for text fields.
* **Default value** - the default value of the field, for boolean fields use
0 or 1
* **Required** - if this checkbox is checked, the post method will raise an
exception if this field is empty. See
:doc:`Modifying datasets `.
* **Read only** - this checkbox is checked, the field value can not be changed
in the interface controls created by the
:doc:`create_inputs `
method on the client.
Lookup tab
==========
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/fields_editr_lookup_jampy.png
:align: center
:alt: fields_editr_lookup_jampy.png
* **Lookup item** - the lookup item for
:doc:`Lookup fields `
* **Lookup field** - the lookup field for
:doc:`Lookup fields `
* **Lookup field 2** - the lookup field 2 for
:doc:`Lookup fields `
* **Lookup field 3** - the lookup field 3 for
:doc:`Lookup fields `
* **Master field** - the master field for
:doc:`Lookup fields `
* **Typeahead** - if this checkbox is checked, typeahead is enabled for the
lookup field
* **Lookup value list** - use it to specify a
:doc:`lookup list `
for an integer field
Interface tab
=============
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/fields_editr_interface_jampy.png
:align: center
:alt: fields_editr_interface_jampy.png
* **Mask** - use this attribute to specify the
:doc:`field_mask `
* **TextArea** - for text fields if this attribute is set the textarea element
will be created for these fields in the :doc:`Edit Form Dialog `
* **Do not sanitize** - set this attribute to prevent default sanitizing of the
field value, see :doc:`Sanitizing `
* **Alignment** - determines the alignment of text in the controls that display
this field.
* **Placeholder** - use this attribute to specify the placeholder that will be
displayed by the field input.
* **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 mark, a pop-up window appears displaying this message.
Interface tab for FILE field
============================
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/fields_editr_file_interface_jampy.png
:align: center
:alt: fields_editr_file_interface_jampy.png
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/file_btns_jampy.png
:align: center
:alt: file_btns_jampy.png
* **Download btn** - uncheck the box to hide the download button (middle)
* **Open btn** - uncheck the box to hide the open button (right)
* **Accept** - the attribute specifies the types of files that can be loaded.
This is an :doc:`Accept string `.
.. note::
Please note that **Accept** attribute is required.
Uploaded files are checked on the server against this attribute.
Interface tab for Image field
=============================
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/fields_editr_image_interface_jampy.png
:align: center
:alt: fields_editr_image_interface_jampy.png
* **View width** - specifies the width of an image in pixels when it is displayed
in the table of the view form. If it not specified, the width is auto
* **View height** - specifies the height of an image in pixels when it is displayed
in the table of the view form. If it not specified, the height is auto
* **Edit width** - specifies the width of an image in pixels when it is displayed
in the edit form. If it not specified, the width is auto
* **Edit height** - specifies the height of an image in pixels when it is displayed
in the edit form. If it not specified, the height is auto
* **Capture from camera** - if this checkbox is set, the user will be able to
capture image from camera by double-click. The image is automatically uploded
to the server,
providing the ".png" is added on Parameters :doc:`Accept string `.
* **Placeholder image** - double-click the image to set the placeholder image,
that will be displayed when field image is not set.
Hold Ctrl key and double-click the image to clear the placeholder image.
Calculation tab
===============
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/fields_editr_calc_jampy.png
:align: center
:alt: fields_editr_calc_jampy.png
* **Calc. object** - specifies the :doc:`Details ` table.
* **Lookup field** - specifies the
:doc:`Lookup field `. For example, the Details table *invoice_table*, *tracks* field,
which is a lookup field to *Name* field on table *Tracks*.
* **Calc. field** - specifies on which field the calculation is performing on.
* **Function** - specifies the server side functions (SUM, COUNT, MIN, MAX, AVG).
================
Edit Form Dialog
================
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**, as well as button ``Desktop`` and ``device``.
The button ``Desktop`` is a default and ``device`` can be used for **tablet** and/or
**mobile phone** inclusion. Each option is independent to each other.
Layout tab
==========
On the **Layout** tab, you can specify the fields that the user can edit,
their order, create tabs and bands for grouping field inputs.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/edit_form_layout_jampy.png
:align: center
:alt: Edit Fields Dialog
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.
* **Columns** - the number of columns that will be created for field inputs (1,2,3,4,6,12).
* **Label size** - select a value that determines the size of the labels
displayed to the right of the field input:
* xSmall
* Small
* Medium
* Large
* xLarge
* **In panel** - if set, the div containing the inputs will have an inset effect
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.
Form tab
========
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/edit_form_form_jampy.png
:align: center
:alt: Edit Form layout tab
On this tab are the controls that you can use to specify the options of the
edit form
* **Form border** - if set, the border will be displayed around the form
* **Form header** - if set, the form header will be created and displayed
containing form title and various buttons
* **History** - if set and
:doc:`saving change history is enabled `, the history
button will be displayed in the form header
* **Close button** - if set, the close button will be created in the upper-right
corner of the form
* **Close on escape** - - if set, pressing on the Escape key will close the form
* **Width** - an integer, the width of the modal form, if not set the value is
600 px
* **Edit details** - click the button to the right of the input field to select
details, that will be available for editing in the edit form
* **Detail height** - an integer, the height of the details displayed in the
edit form, if not set, the height of the detail table is 262px
* **Buttons on top** - if this check box is checked the buttons are displayed on
the top of the view form, when form has a default form template
* **Modeless form** - if this check box is checked the form will be modeless,
otherwise - modal.
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.
================
View Form Dialog
================
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**, as well as button ``Desktop`` and ``device``.
The button ``Desktop`` is a default and a ``device`` can be used for **tablet** and/or
**mobile phone** inclusion. Each option is independent to each other.
Layout tab
==========
On the Layout tab, you can specify how the table is displayed in the view form of
the item.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/view_form_layout_jampy.png
:align: center
:alt: View Form Layout tab
Setting table fields
--------------------
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:
* 100px
* 100
* 50%
* 2cm
Setting table options
---------------------
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:
* **Multiple selection** - if 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
:doc:`selections `
attribute.
* **Dblclick edit** - if set, the edit form will be displayed when the user
double-clicks on the table row.
* **Number of rows** - an integer number, if set, specifies the number of rows
displayed by the table, otherwise, if **Height** is not specified,
the application calculates the height of the table, based on the page height
* **Height** - an integer number, if set, specifies the height of the table in
pixels, otherwise , if **Number of rows** is not specified, the application
calculates the height of the table, based on the page height
* **Row lines** - an integer, specifying 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 row cells
* **Selected row lines** - an integer value, if **Row lines** is set and
this value is greater that 0, it specifies the minimal number of
lines of text displayed in the selected row of the table
* **Freeze columns** - an integer, 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. This option for V7 is an attribute of the item.
* **Sort fields** - click the button to the right of the input field to open the list of fields and
select the fields by which you can sort the contents of the table by clicking
in the corresponding column header of the table.
* **Summary fields** - click the button to the right of the input field to open the list of fields
and the fields for which the summary will be calculated and displayed in the
corresponding column footer. For for numeric fields sums will be calculated,
for not numeric fields - the number of records.
You can get or change these values programmatically on the client by using the
:doc:`table_options `
attribute of the item.
Form tab
========
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/view_form_form_jampy.png
:align: center
:alt: View Form Form tab
On this tab are the controls that you can use to specify the options of the
view form
* **Form border** - if set, the border will be displayed around the form
* **Form header** - if set, the form header will be created and displayed
containing form title and various buttons
* **History** - if set and
:doc:`saving change history is enabled `, the history
button will be displayed in the form header
* **Refresh button** - if set, the refresh button will be created in the form
header, that will allow users to refresh the page
* **Search** - if set, the search input will be created in the form header
* **Default search field** - click the button to the right of the input field to select
a default search field
* **Filters** - if set and there are visible filters, the filter button will
be created in the form header
* **Close button** - if set, the close button will be created in the upper-right
corner of the form
* **Close on escape** - - if set, pressing on the Escape key will close the form
* **Width** - an integer, the width of the modal form, if not set the value is
600 px
* **View details** - click the button to the right of the input field to select
details, that will be displayed in the view form
* **Detail height** - an integer, the height of the details displayed in the
view form, if not set, the height of the detail table is 232px
* **Buttons on top** - if this check box is checked the buttons are displayed on
the top of the view form, when form has a default form template
You can get or change these values programmatically on the client by using the
:doc:`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.
==============
Filters Dialog
==============
Use **Filters Dialog** to create and modify item filters. See
:doc:`Filters `
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/filters_dialog_jampy.png
:align: center
:alt: Filters Dialog
To add or edit a filter click on the appropriate button on the form. The
following form will appear:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/filter_editor_jampy.png
:align: center
:alt: Filter Editor
Fill in the following fields:
* **Field** - the field which will be used to filter records.
* **Caption** - the filter name that appears to users.
* **Name** - the name of the filter that will be used in programming code to
get access to the filter object. It should be a valid python identifier.
* **Filter type** - select filter type.
* **Placeholder** - use this attribute to specify the placeholder that will be
displayed by the field input.
* **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 mark, a pop-up window appears displaying this message.
* **Visible** - if this checkbox is not checked, this filter will not be displayed
in the item Filters dialog.
Use the up and down arrows to place the filters in the order in which they will
be displayed. See
:doc:`create_filter_inputs `
==============
Details Dialog
==============
Use this dialog to setup details of an item. See
:doc:`Details `.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/details_dialog_jampy.png
:align: center
:alt: Details Dialog
The **Details Dialog** has two panels. The left panel lists details that
have been added. The right panel has 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.
============
Order Dialog
============
The **Order Dialog** opens when a developer selects the item in the
Application builder (see
:doc:`Items `
) and clicks on the **Order** button to specify how records will be ordered by
default. See
:doc:`open `
method
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/records_order_dialog_jampy.png
:align: center
:alt: Order Dialog
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.
==============
Indices Dialog
==============
The **Indices Dialog** lists the indices that were created for the item table in
the project database.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/indices_dialog_jampy.png
:align: center
:alt: Indices Dialog
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:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/index_dialog_jampy.png
:align: center
:alt: Index Dialog
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.
==============
Reports Dialog
==============
The **Reports Dialog** opens when a developer selects the item in the
Application builder (see
:doc:`Items `
) and clicks on the **Reports** 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.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/reports_dialog_jampy.png
:align: center
:alt: Reports Dialog
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.
=======
Details
=======
The Detail in Jam.py V7 is any database table linked in a way of Master/Details
relationship.
To group the database tables logically, we might use the :doc:`Details group `
to "store" a detail of an item, as seen on below screenshot.
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:
* **Client module** - click on this button to open the
:doc:`Code editor `
to edit client module of a detail, see
:doc:`Working with modules `.
* **Server module** - click on this button to open the
:doc:`Code editor `
to edit server module of a detail, see
:doc:`Working with modules `.
* **View Form** - use this button to invoke the
:doc:`View Form Dialog `
to set the fields to be displayed in tables on the client and their order,
by default. See
:doc:`create_table `
method
* **Edit Form** - use this button to invoke the
:doc:`Edit Form Dialog `
to set the fields to be displayed in edit forms on the client and their
order, by default. See
:doc:`create_inputs `
method.
* **Filters** - use this button to invoke the
:doc:`Filters Dialog `
to create, modify and delete item filters. See
:doc:`Filters `.
* **Details** - use this button to invoke the
:doc:`Details Dialog `
to add or remove details linked to the item.
* **Order** - use this button to invoke the
:doc:`Order Dialog `
to specify how records will be ordered by default. See
:doc:`open `
method
* **Indices** - сlick this button to open the
:doc:`Indices Dialog `
to create and delete indices for the item database table.
..
* **Foreign keys** - сlick this button to open the
:doc:`Foreign Keys Dialog `
to create foreign keys for the database table.
* **Reports** - сlick this button to open the
:doc:`Reports Dialog `
to specify reports that could printed for the item.
A new project has a function that can be used to create a drop-up
button to print the reports.
* **Privileges** - click this button to open a dialog to configure the
privileges assigned to user roles for this item.
Use **Edit** button at the bottom of the page to change ``item_name`` or
``caption`` of a `detail`.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/details_jampy.png
:align: center
:alt: Jam.py details
==================
Lookup List Dialog
==================
Lookup list is a list of integer-text pairs that can used as a datasource for
:doc:`lookup fields `.
.. note::
The length of the lookup list should not exceed 10
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/lookup_lists_jampy.png
:align: center
:alt: Lookup List Dialog
Click on the **Edit**/**New** buttons to edit/create a lookup list.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/lookup_list_jampy.png
:align: center
:alt: Lookup List
Then use the **Edit**/**New** buttons to edit/add a lookup pairs to the list.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/lookup_paire_jampy.png
:align: center
:alt: Lookup Pair
==================================
Importing existing database tables
==================================
For importing existing database tables:
* Create a new project with connection to existing database.
* Select Project node and click Database button. Set
:doc:`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.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/Import_tables_jampy.png
:align: center
:alt: import_tables_jampy.png
* In the
:doc:`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::
The indexes are not imported.
===============================================
Saving audit trail/change history made by users
===============================================
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:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/history_item_jampy.png
:align: center
:alt: History item
In the dialog that will appear click on the Create history item button
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/new_hist_item_jampy.png
:align: center
:alt: New history item
The following message will appear when the item will be created:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/hist_item_created_jampy.png
:align: center
:alt: History item created
After that you have to set **Keep history** attribute of an item to save the history
its changes:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/item_editor_dialog_jampy.png
:align: center
:alt: Keep history attribute
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.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/invoices_hist_jampy.png
:align: center
:alt: Invoices history
Or you can do it using the
:doc:`show_history `
method
.. note::
Changes are saved when dataset changes are applied to the database using
apply method (:doc:`client `/:doc:`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.
==============
Record locking
==============
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
:doc:`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 create an table field that will store record version.
The field type is ``integer``.
After that we can set **Edit lock** attribute in the
:doc:`Item Editor Dialog `:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/item_editor_dialog_jampy.png
:align: center
:alt: Keep history attribute
The message displayed for the locked record:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/record_locking_msg_jampy.png
:scale: 50%
:align: center
:alt: Locked Record message
================
Language support
================
Use Language Dialog to add, select and change your language.
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/lang_dialog_jampy.png
:align: center
:alt: lang_dialog_jampy.png
Language locale
===============
Use language locale to set up how the field value will be displayed. See
:doc:`display_text `
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/lang_locale_jampy.png
:align: center
:alt: lang_locale_jampy.png
Language translation
====================
See :doc:`Language translation `
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/lang_translation_jampy.png
:align: center
:alt: lang_translation_jampy.png
====================
Language translation
====================
All language translations are stored in the langs.sqlite database in the "jam"
folder in the package.
.. note::
.. role:: red
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 your translation to a file!!!**
If you want your language translation to be included to Jam.py package,
export it to a file and contact the package maintainer on GitHub to include it.
Or, send the file to Jam.py mailgroup.
Please note that Jam.py is constantly evolving and by submitting your translation
you might need 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
**Kazakhstan translation**:
Нельзя удалить поле %(field)s. Используется в определении полей:%(fields)s
=============
Accept string
=============
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/*
======================
Builder customisation
======================
Here are Andrew's notes:
.. admonition:: Customising Application Builder
The Application Builder project is located in the 'builder' folder of the distribution.
This project is used to create a new version of App Builder using an older version.
The project has everything needed to develop the Builder further.
The application is started as any other Jam.py application.
The development process is similar to the development of a regular Jam.py application, except that all server code must be located in the task Server module.
All functions in Server module that are called from client code using the server method should be registered in the register_events function at the end of the module using the register method, and server item events should be defined in this function.
After making and testing changes, the "Prepare files" button is used. The application will create necessary files in the jam_files folder.
If the folder does not exist, it will create one.
Content of this folder should be copied to the jam folder of the Jam.py distribution package.
.. admonition:: Accessing Application Builder app
When a web application is created with wsgi.py, the builder Task tree is created – the admin object.
The admin loads the task using get_info method from the builder_structure.info file that was saved when the "Prepare files" button was clicked.
This is done in the admin.py module that is located in the admin folder of the package.
The task tree of the project is created when the first request is received by the web application.
The task.py module from the "admin" folder of the package contains the code that creates the project Task tree.
It uses the administrator user to read the data from admin.sqlite database.
To speed up the process, the information from corresponding tables is loaded to dictionaries.
First, start with forking `Jam.py-v7`_.
.. _Jam.py-v7: https://github.com/jam-py-v5/jam-py-v7
Next, clone your fork::
git clone https://github.com/YourGitHubName/jam-py-v7.git
and start the server::
cd jam-py-v7/builder
./server.py
Open a Web browser and enter
.. code-block:: console
127.0.0.1:8080/builder.html
in the address bar. This is the application builder source. Hence, everything
displayed within the ``builder.html`` page when building the application, is a direct
result of modifying anything in here.
Open a Web browser and enter
.. code-block:: console
127.0.0.1:8080
in the address bar. This is the application builder content displayed as usual for any
Jam.py application. However, it has one additional feature, the ``Prepare files`` button.
The application features and look directly depend on Jam.py installed version.
No changes made within a builder will show in here if the newer run time files
were not installed.
After customisation is finished, the ``Prepare files`` clicked, we copy all the
files from ``jam_files`` folder into to distribution folders::
cp -fr jam_files/* ../jam/.
On MS Windows::
xcopy jam_files\* ..\jam\. /E /H /C /I /Y
To increase the Jam.py version number, we edit below file::
vi ../jam/__init__.py
We can now install updated version as usual with the new version number.
If all good to go, commit and create a Github ``pull`` request with the changes.
For the project maintainers, to initiate Github Actions, accomodate tag number below::
git push && git tag 7.0.XX && git push --tags
===================
Foreign Keys Dialog
===================
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
:doc:`Foreign keys topic ` in FAQ
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/foreign_keys_dialog_jampy.png
:align: center
:alt: Foreign Keys Dialog
To do so click the **New** button, select the field and click **OK**.
========
Routing
========
The Jam.py v7 introduced routing. As Jam.py v5 is a SPA (Single Page Application), there was no need for routing.
On the other hand, there was a need to implement, for example, the User registration page with Jam.py v5.
The solution for this problem was similar to :doc:`creating a registration form `.
As seen, the **register.html** file is using JavaScript AJAX code. Hence, any additional page would need a similar approach, if
interacting with the database.
In Jam.py v7, the solution is to process every request in the ``on_request`` event handler and defining a response on a valid request.
This enables us to do a custom login and registration forms, where custom errors can be created. For example: "User does not exist!", "Wrong password", etc.
The new **login.html** page with the error message:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/custom_login_error_jampy.png
:align: center
:alt: Custom Login Form
The new **register.html** page (no AJAX):
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/register_form_v7_jampy.png
:align: center
:alt: Registration Form
Routing code
=============
As mentioned, the ``on_request`` is used.
The below code should exist on "Task/Server Module". No Python logic is included below to make it simple to read:
.. code-block:: Python
def on_request(task, request):
parts = request.path.strip('/').split('/')
if not parts[0]:
if task.logged_in(request):
return task.serve_page('index.html')
else:
return task.redirect('/login.html')
elif parts[0] == 'login.html':
.
.
elif parts[0] == 'register.html':
.
.
return task.serve_page('register.html')
.
.
return task.redirect('/login.html')
The working example for serving robots.txt file:
.. code-block:: Python
def on_request(task, request):
parts = request.path.strip('/').split('/')
if not parts[0]:
if task.logged_in(request):
return task.serve_page('index.html')
else:
return task.redirect('/login.html')
elif parts[0] == 'robots.txt':
if task.logged_in(request):
return task.serve_page('robots.txt')
The ``robots.txt`` file should exist within the application folder.
See also
========
:doc:`serve_page `
:doc:`redirect `
:doc:`on_request `
==========
Sanitizing
==========
To prevent Cross Site Scripting (XSS) attacks, Jam.py sanitizes field values
displayed in the table columns.
For example, if field contains the following text:
.. code-block:: html
Norway
when un-sanitized, it will be displayed in the table column as follows:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/unsanitized_jampy.png
:align: center
:alt: unsanitized_jampy.png
When the field text is sanitized, it is transformed to the following:
.. code-block:: html
"<span style='color: red'>Norway</span>"
as you can see symbols '<' and '>' are replaced with '<' and '>'
and the table column will be displayed this way:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/sanitized_jampy.png
:align: center
:alt: sanitized_jampy.png
There are two ways to prevent sanitizing.
First is to set **Do not sanitize** attribute in the Interface tab in the
:doc:`Field Editor Dialog `
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/fields_editr_interface_jampy.png
:align: center
:alt: do_not_sanitize_jampy.png
Second is to write the
:doc:`on_field_get_html `
event handler. If the this event handler returns a value it is not sanitized.
======================
Jam.py class reference
======================
Server side is implemented in Python and uses Werkzeug library, the client side in
JavaScript and uses JQuery and Bootstrap
.. toctree::
:maxdepth: 2
client/index
server/index
exceptions/index
========================================
Client side (javascript) class reference
========================================
All objects of the framework represent a :doc:`task tree `.
Bellow is classes for each kind of task tree objects:
.. toctree::
:maxdepth: 1
abstractitem_api
task_api
item_group_api
item_api
detail_api
report_group_api
report_api
field_api
filter_api
==================
AbstractItem class
==================
.. js:class:: AbstractItem
**domain**: client
**language**: javascript
AbstractItem class is the ancestor for all item objects of the
:doc:`task tree `
Below the attributes and methods of the class are listed.
Attributes
==========
.. toctree::
:maxdepth: 1
:glob:
abstr_item/at_*
Methods
=======
.. toctree::
:maxdepth: 1
:glob:
abstr_item/m_*
==
ID
==
.. js:attribute:: ID
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
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
============
.. js:attribute:: item_caption
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
Item_caption attribute specifies the name of the item that appears to users
=========
item_name
=========
.. js:attribute:: item_name
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
Specifies the name of the item as referenced in code.
Use item_name to refer to the item in code.
=========
item_type
=========
.. js:attribute:: item_type
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
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:
* “task”,
* “items”,
* “details”,
* “reports”,
* “item”,
* “detail_item”,
* “report”,
* “detail”
=====
items
=====
.. js:attribute:: items
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
Lists all items owned by the item.
Use items to access any of the item owned by this object.
=====
owner
=====
Indicates the item that owns this item.
.. js:attribute:: owner
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
Use owner to find the owner of an item.
====
task
====
Indicates the root of the :doc:`task tree ` that owns this item.
.. js:attribute:: task
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
Use task attribute to find the root of the :doc:`task tree `
of which the item is a member.
=====
abort
=====
.. js:function:: abort(message)
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
Use **abort** method to throw exception.
It can be usefull when you need to abort execution of some 'on_before' events.
Example
=======
The following code will throw exception with the text:
execution aborted: invoice_table - a quantity value is required
.. code-block:: js
function on_before_post(item) {
if (item.quantity.value === 0) {
item.abort('a quantity value is required');
}
}
=====
alert
=====
.. js:function:: alert(mess, options)
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
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:
* 'info',
* 'error',
* 'success'
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.
Example
=======
.. code-block:: js
item.alert_error('Failed to send the mail: ' + err);
item.alert('Successfully sent the mail');
========
can_view
========
.. js:function:: can_view()
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
Use **can_view** method to determine if a user have a right to get access to an
:doc:`item `
dataset or to see report generated by
:doc:`report `
when the project
:doc:`Safe mode parameter `
is set. If the project
:doc:`Safe mode parameter `
is not set the method always returns true.
The user privileges are set in the :doc:`roles node ` of the project
tree.
Example
=======
.. code-block:: js
if (item.visible && item.can_view()) {
$("#submenu")
.append($('')
.append(
$('')
.text(item.item_caption)
.data('item', item);
)
);
}
=========
each_item
=========
.. js:function:: each_item(function(item))
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
Use each_item method to iterate over :doc:`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.
Example
=======
The following code will output all catalogs of the project in a browser console:
.. code-block:: js
function on_page_loaded(task) {
task.catalogs.each_item(function(item) {
console.log(item.item_name);
})
}
============
hide_message
============
.. js:function:: hide_message(form)
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
Use **hide_message** method to close a modal form created by
:doc:`message ` method
The **form** parameter is a JQuery object returned by
:doc:`message ` method.
==========
item_by_ID
==========
.. js:function:: item_by_ID(ID)
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
item_by_ID searches among all items of the project :doc:`task tree `,
starting with the current item, for an item whose :doc:`ID ` attribute is
equal to the ID parameter.
===========
load_module
===========
.. js:function:: load_module(callback)
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
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 server is executed asynchronously.
Example
=======
Bellow, the do_some_work function is executed only when an item module has been
loaded:
.. code-block:: js
function some_work(item) {
item.load_module(do_some_work);
}
function do_some_work(item) {
// some code
}
See also
========
:doc:`Working with modules `
:doc:`load_modules `
:doc:`load_script `
============
load_modules
============
.. js:function:: load_modules(module_array, callback)
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
Use **load_modules** method to dynamically load specified modules before
executing the **callback**.
The method works the same way as
:doc:`load_module `,
only loads and initializes all modules of items specified in the
**module_array**.
Example
=======
Bellow, the do_some_work function is executed only when modules of the item and
its owner has been loaded:
.. code-block:: js
function some_work(item) {
item.load_modules([item, item.owner], do_some_work);
}
function do_some_work(item) {
// some code
}
See also
========
:doc:`Working with modules `
:doc:`load_module `
:doc:`load_script `
===========
load_script
===========
.. js:function:: load_script(js_filename, callback, onload)
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
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 server is executed asynchronously.
Example
=======
Bellow, the do_some_work function is executed only when *lib.js* file from server
*js* directory has been loaded:
.. code-block:: js
function some_work(item) {
item.load_script('js/lib.js', do_some_work);
}
function do_some_work(item) {
// some code
}
See also
========
:doc:`Working with modules `
:doc:`load_module `
:doc:`load_modules `
=======
message
=======
.. js:function:: message(mess, options)
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
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 attributes:
* **title** - the title of the form,
* **width** - the width of the form, the default width is 400px
* **height** - the height of the form,
* **margin** - use the margin attribute to define margins of the form body
* **text_center** - if true, the body tags will be centered,
the default value is false,
* **buttons** - an object that define buttons that will be created in the footer
of the form, keys of the object are button names, values - functions, that
will be executed when button clicked,
* **button_min_width** - the min width of the buttons, the default value is 100px,
* **center_buttons** - if true, the buttons will be centered,
the default value is false,
* **close_button** - if this value is true, an application will create a close
button in the upper-right corner of the form, the default value is true,
* **close_on_escape** - if true, the form will be closed, when user press Escape,
the default value is true,
* **print** - if this value is true, an application will create a print button in
the upper-right corner of the form to print the body of the form,
the default value is false
The method returns a jquery object of the form. To programmatically close the
form pass this object to :doc:`hide_message ` method.
Examples
========
The following code will create a yes-no-cancel dialog:
.. code-block:: js
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});
}
.. code-block:: js
task.message(
'
',
{title: 'Jam.py framework', margin: 0, text_center: true, buttons: {"Yes": undefined, "No": undefined, "Cancel": undefined},
center_buttons: true}
);
The result of the code above will be:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/message_jampy.png
:width: 400
:align: center
:alt: Message method example
========
question
========
Creates a modal form with **Yes**, **No** buttons
.. js:function:: question(mess, yes_callback, no_callback, options)
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
Use **question** to create a modal form with **Yes** and **No** 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 **Yes** or **No** button, respectively,
and then the form will be closed.
Example
=======
The following code creates a modal form, and delete selected record
record when the user clicks the Yes button:
.. code-block:: js
item.question('Delete record?',
function() {
item.delete();
}
);
The result of the code above will be:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/delete_jampy.png
:align: center
:alt: Message delete record
======
server
======
.. js:function:: server(func_name, params, callback)
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
Use ``server`` method to execute a function defined in the server module of an item.
``Server`` 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.
Example
=======
The function defined in the **Invoices** journal ``Server Module``:
.. code-block:: py
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:
.. code-block:: js
task.invoices.server('get_total', [17], function(total, err) {
if (err) {
throw err;
}
else {
console.log(total);
}
});
=======
warning
=======
.. js:function:: warning(mess, callback)
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
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.
Example
=======
.. code-block:: js
item.warning('No record selected.');
=============
yes_no_cancel
=============
.. js:function:: yes_no_cancel(mess, yes_callback, no_callback, cancel_callback)
**domain**: client
**language**: javascript
**class** :doc:`AbstractItem `
Description
===========
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.
Example
=======
The following code is executed when user clicks on the close button in the upper
right corner of an item edit form.
.. code-block:: js
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;
}
The result of the code above will be:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/yes_no_cancel_jampy.png
:align: center
:alt: Message Yes, No, Cancel
==========
Task class
==========
.. js:class:: Task
**domain**: client
**language**: javascript
Task class is used to create the root of the
:doc:`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
:doc:`AbstractItem class `
Attributes
==========
.. toctree::
:maxdepth: 1
:glob:
task/at_*
Methods
=======
.. toctree::
:maxdepth: 1
:glob:
task/m_*
Events
======
.. toctree::
:maxdepth: 1
:glob:
task/on_*
===============
forms_container
===============
.. js:attribute:: forms_container
**domain**: client
**language**: javascript
**class** :doc:`Task `
Description
===========
The ``forms_container`` is a JQuery object in which the application will create
forms.
To initialize ``forms_container`` use the
:doc:`set_forms_container ` method or the
:doc:`create_menu ` method.
The default code uses the :doc:`create_menu ` method.
See also
========
:doc:`forms_in_tabs `
:doc:`create_menu `
:doc:`set_forms_container `
=============
forms_in_tabs
=============
.. js:attribute:: forms_in_tabs
**domain**: client
**language**: javascript
**class** :doc:`Task `
Description
===========
If the ``forms_in_tabs`` attribute is set and
:doc:`forms_container ` is specified the application will
create forms in tabs.
This attribute can be set in the **Interface** tab of
:doc:`Parameters `.
=========
safe_mode
=========
.. js:attribute:: safe_mode
**domain**: client
**language**: javascript
**class** :doc:`Task `
Description
===========
Check the ``safe_mode`` attribute to determine if the
:doc:`safe mode `
parameter of the project is set.
Example
=======
.. code-block:: js
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"));
}
See also
========
:doc:`Parameters `
:doc:`user_info `
:doc:`on_page_loaded `
=========
templates
=========
.. js:attribute:: templates
**domain**: client
**language**: javascript
**class** :doc:`Task `
Description
===========
The ``templates`` attribute stores the form templates of the project.
See also
========
:doc:`Form templates `
:doc:`Forms `
=========
user_info
=========
.. js:attribute:: user_info
**domain**: client
**language**: javascript
**class** :doc:`Task `
Description
===========
Use ``user_info`` attribute to get user information when project
:doc:`Safe mode parameter `
is set.
``user_info`` is an object that has the following attributes:
* ``user_id`` - the user id
* ``user_name`` - the user name
* ``role_id`` - user role id
* ``role_name`` - the role assigned to the user
* ``admin`` - if true the user can work in the Application builder
If safe mode is false the ``user_info`` attribute is an empty
object.
Example
=======
.. code-block:: js
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
}
See also
========
:doc:`load `
:doc:`login `
:doc:`logout `
:doc:`Users `
:doc:`Roles `
=======
add_tab
=======
.. js:function:: add_tab(container, tab_name, options)
**domain**: client
**language**: javascript
**class** :doc:`Task `
Description
===========
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 tab
* ``show_close_btn`` - if it is set to ``true`` the close tab button will appear
that can be used to close the tab
* ``set_active`` - if it is set to ``true`` the new tab will became active
* ``on_close`` - a callback function that will be called when the close tab button
is clicked
The function returns the JQuery object of the div with ``tab-pane`` class that
will be displayed when tab became active.
Example
=======
The following code will create tabs for editing Customers catalog. It uses
:doc:`create_inputs `
method:
.. code-block:: js
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:
.. code-block:: html
See also
========
:doc:`init_tabs `
:doc:`close_tab `
=========
close_tab
=========
.. js:function:: close_tab(container, tab_id)
**domain**: client
**language**: javascript
**class** :doc:`Task `
Description
===========
Use the ``close_tab`` method to close tab in the ``container`` identified by
``tab_id``.
See also
========
:doc:`init_tabs `
:doc:`add_tab `
===========
create_menu
===========
.. js:function:: create_menu: function(menu, forms_container, options)
**domain**: client
**language**: javascript
**class** :doc:`Task `
Description
===========
The ``create_menu`` method created a menu based on the project
:doc:`task tree `.
If display forms in tabs attribute of the
:doc:`project parameters ` is set, initializes
tabs that will be created to display forms.
It iterates through the items of the
:doc:`task tree `
and adds items to the menu for which the visible attribute is set to true,
and the user has the right to view them.
The method uses to assign on click event to the menu items so that for reports the
:doc:`print `
method will be executed when a user clicks it and the
:doc:`view `
method will be executed for other items.
The following parameters could be passed to the method:
* ``menu`` - a JQuery object of the menu element from index.html file
* ``forms_container`` a JQuery object of the element that will contain the forms
created by the
:doc:`view `
method
* ``options`` - an object that can have the following attributes:
* ``custom_menu`` - use this option to create a custom menu, see below for details
* ``view_first`` - if it is true the view form of the first item in the menu will
be displayed after menu is created, the default value is ``false``
* ``create_single_group`` - if it is true and only one group in the task tree
has items the menu item for the group will be created that have a drop down
menu for group items, otherwise the menuitems for each item will be created,
the default value is ``false``
* ``splash_screen`` - an html that will be displayed in the forms_container when
all tabs are closed
Custom menu option
------------------
To create your own custom menu you must set a custom_menu option.
This option is a list of menu objects, each object can be:
* Jam.py item or item group
* array: the first element of the array is the name of the menu item, and
the second is the list of menu objects
* object with one attribute: the key of the attribute is the name of menu item
and the value - a list of menu objects
* object with one attribute: the key of the attribute is the name of menu item
and the value - function to be executed when the menu item is clicked
To add a separator, an empty string ('') can be added to the list of menu objects
Example
=======
Modified a ``on_page_loaded`` with:
.. code-block:: js
task.create_menu($("#menu"), $("#content"), {
custom_menu: menu,
splash_screen: '
Jam.py Demo Application
',
view_first: true
});
An example with custom menu in the same ``on_page_loaded``:
.. code-block:: js
let menu = [
['First', [task.invoices, task.customers]],
{'Second': [task.catalogs, '', task.reports]},
{Third: [task.tracks, {Params: function() {alert('params clicked')}}]},
{Fourth: [task.task.analytics, {'Artists list': [task.artists]}]},
task.reports,
{Params: function() {alert('params clicked')}},
];
task.create_menu($("#menu"), $("#content"), {
custom_menu: menu,
splash_screen: '
Jam.py Demo Application
',
view_first: true
});
For Demo application, the above will result with:
.. image:: https://jampy-docs-v7.readthedocs.io/en/latest/_images/custom_menu_jampy.png
:align: center
:alt: Custom Menu
=========
init_tabs
=========
.. js:function:: init_tabs(container, tabs_position)
**domain**: client
**language**: javascript
**class** :doc:`Task `
Description
===========
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
:doc:`add_tab `
method will be positioned. It is string that can be one of the following values:
* tabs-below
* tabs-left
* tabs-right
If this parameter is omitted tabs will be positioned at the top of the container.
After this method is called you can use the
:doc:`add_tab `
method to create tabs.
See also
========
:doc:`add_tab `
:doc:`close_tab `
====
load
====
.. js:function:: load(callback)
**domain**: client
**language**: javascript
**class** :doc:`Task `
Description
===========
``Load`` method loads the project :doc:`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
:doc:`task tree ` from the server and
initilizes it (see :doc:`workflow `).
After that the application triggers
:doc:`on_page_loaded ` event.
Example
=======
The following code is from the project index.html file.
.. code-block:: html
See also
========
:doc:`login `
:doc:`logout `
:doc:`user_info `
:doc:`Users `
:doc:`Roles `
=====
login
=====
.. js:function:: login(callback)
**domain**: client
**language**: javascript
**class** :doc:`Task `
Description
===========
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 :doc:`load ` method
when the project
:doc:`Safe mode parameter `
See also
========
:doc:`load `
:doc:`logout `
:doc:`user_info `
:doc:`Users `
:doc:`Roles `
======
logout
======
.. js:function:: logout()
**domain**: client
**language**: javascript
**class** :doc:`Task `
Description
===========
Call ``logout`` to logout a user.
Example
=======
.. code-block:: js
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
}
See also
========
:doc:`load `
:doc:`login `
:doc:`user_info `
:doc:`Users `
:doc:`Roles `
===================
set_forms_container
===================
.. js:function:: set_forms_container(container, options)
**domain**: client
**language**: javascript
**class** :doc:`Task `
Description
===========
The ``set_forms_container`` can be used to initialize the
:doc:`forms_container ` attribute that will contain forms of
the application.
If the :doc:`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 closed
Example
=======
.. code-block:: js
task.set_forms_container($("#content"), {
splash_screen: '
Jam.py Demo Application
'
});
See also
========
:doc:`forms_container `
:doc:`forms_in_tabs `
:doc:`create_menu `
======
upload
======
.. js:function:: upload(options)
**domain**: client
**language**: javascript
**class** :doc:`Task `
Description
===========
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 true
* ``accept`` - the attribute specifies the types of files that can be submitted
through a file upload, see :doc:`Accept string `
.. note::
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 :doc:`Project parameters `.
========================
on_edit_form_close_query
========================
.. js:function:: on_edit_form_created(item)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_edit_form_close_query`` event is triggered by the
:doc:`close_edit_form `
method of the item.
The ``item`` parameter is the item that triggered the event.
Example:
.. code:: js
function on_edit_form_close_query(item) {
return true;
}
See also
========
:doc:`Forms `
:doc:`close_edit_form `
====================
on_edit_form_created
====================
.. js:function:: on_edit_form_created(item)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_edit_form_created`` event is triggered by the
:doc:`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
:doc:`create_edit_form `
method has been called.
See also
========
:doc:`Forms `
:doc:`create_edit_form `
====================
on_edit_form_keydown
====================
on_edit_form_keydown(item, event)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_edit_form_keydown`` event is triggered when the keydown event
occurs for the
:doc:`edit_form `
of the item.
The ``item`` parameter is the item that triggered the event.
The ``event`` is JQuery event object.
See also
========
:doc:`Forms `
:doc:`create_edit_form `
==================
on_edit_form_keyup
==================
on_edit_form_keyup(item, event)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_edit_form_keyup`` event is triggered when the keyup event
occurs for the
:doc:`edit_form `
of the item.
The ``item`` parameter is the item that triggered the event.
The ``event`` is JQuery event object.
Example for ``CTR+Enter``:
.. code-block:: js
function on_edit_form_keyup(item, event) {
if (event.keyCode === 13 && event.ctrlKey === true){
item.edit_form.find("#ok-btn").focus();
item.apply_record();
}
}
See also
========
:doc:`Forms `
:doc:`create_edit_form `
==================
on_edit_form_shown
==================
.. js:function:: on_edit_form_shown(item)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_edit_form_shown`` event is triggered by the
:doc:`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
:doc:`create_edit_form `
method has been called.
See also
========
:doc:`Forms `
:doc:`create_edit_form `
==========================
on_filter_form_close_query
==========================
.. js:function:: on_filter_form_close_query(item)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_filter_form_close_query`` event is triggered by the
:doc:`close_filter_form `
method of the item.
The ``item`` parameter is the item that triggered the event.
See also
========
:doc:`Forms `
:doc:`create_filter_form `
:doc:`close_filter_form `
======================
on_filter_form_created
======================
.. js:function:: on_filter_form_created(item)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_filter_form_created`` event is triggered by the
:doc:`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
:doc:`create_filter_form `
method has been called.
See also
========
:doc:`Forms `
:doc:`create_filter_form `
====================
on_filter_form_shown
====================
.. js:function:: on_filter_form_shown(item)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_filter_form_shown`` event is triggered by the
:doc:`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
:doc:`create_filter_form `
method has been called.
See also
========
:doc:`Forms `
:doc:`create_filter_form `
==============
on_page_loaded
==============
on_page_loaded(task)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_page_loaded`` event is the first event triggered on the client. See
:doc:`Workflow `.
Use it to initialize the client.
The ``task`` parameter is the root of the client
:doc:`task tree `.
See also
========
:doc:`Workflow `
:doc:`Task tree `
=========================
on_param_form_close_query
=========================
.. js:function:: on_param_form_close_query(item)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_param_form_close_query`` event is triggered by the
:doc:`close_param_form `
method.
The ``report`` parameter is the report that triggered the event.
See also
========
:doc:`Forms `
:doc:`Client-side report programming `
:doc:`close_param_form `
=====================
on_param_form_created
=====================
.. js:function:: on_param_form_created(item)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_param_form_created`` event is triggered by the
:doc:`create_param_form `
method, that, usually, is called by then
:doc:`print `
method.
The ``report`` parameter is the report that triggered the event.
See also
========
:doc:`Forms `
:doc:`Client-side report programming `
:doc:`print `
:doc:`create_param_form `
===================
on_param_form_shown
===================
.. js:function:: on_param_form_shown(item)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_param_form_shown`` event is triggered by the
:doc:`create_param_form `
method, that, usually, is called by then
:doc:`print `
method.
The ``report`` parameter is the report that triggered the event.
See also
========
:doc:`Forms `
:doc:`Client-side report programming `
:doc:`print `
:doc:`create_param_form `
========================
on_view_form_close_query
========================
.. js:function:: on_view_form_close_query(item)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_view_form_close_query`` event is triggered by the
:doc:`close_view_form `
method of the item.
The ``item`` parameter is the item that triggered the event.
See also
========
:doc:`Forms `
:doc:`close_view_form `
====================
on_view_form_created
====================
.. js:function:: on_view_form_created(item)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_view_form_created`` event is triggered by the
:doc:`view `
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
:doc:`view `
method has been called.
See also
========
:doc:`Forms `
:doc:`view `
====================
on_view_form_keydown
====================
on_view_form_keydown(item, event)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_view_form_keydown`` event is triggered when the keydown event
occurs for the
:doc:`view_form `
of the item.
The ``item`` parameter is the item that triggered the event.
The ``event`` is JQuery event object.
See also
========
:doc:`Forms `
:doc:`view `
==================
on_view_form_keyup
==================
on_view_form_keyup(item, event)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_view_form_keyup`` event is triggered when the keyup event
occurs for the
:doc:`view_form `
of the item.
The ``item`` parameter is the item that triggered the event.
The ``event`` is JQuery event object.
Example for ``CTRL+Ins`` and ``CTRL+Del``:
.. code-block:: js
function on_view_form_keyup(item, event) {
if (event.keyCode === 45 && event.ctrlKey === true){
if (item.master) {
item.append_record();
}
else {
item.insert_record();
}
}
else if (event.keyCode === 46 && event.ctrlKey === true){
// item.delete_record();
item.alert('Cannot be deleted on Demo!');
}
}
See also
========
:doc:`Forms `
:doc:`view `
==================
on_view_form_shown
==================
.. js:function:: on_view_form_shown(item)
**domain**: client
**language**: javascript
**class** :doc:`Task class `
Description
===========
The ``on_view_form_shown`` event is triggered by the
:doc:`view `
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
:doc:`view `
method has been called.
See also
========
:doc:`Forms `
:doc:`view `
===========
Group class
===========
.. js:class:: Group
**domain**: client
**language**: javascript
Group class is used to create group objects of the
:doc:`task tree `
Below the events of the class are listed.
It, as well, inherits attributes and methods of its ancestor class
:doc:`AbstractItem class `
Events
======
.. toctree::
:maxdepth: 1
:glob:
group/on_*
========================
on_edit_form_close_query
========================
.. js:function:: on_edit_form_close_query(item)
**domain**: client
**language**: javascript
**class** :doc:`Group class `
Description
===========
The ``on_edit_form_close_query`` event is triggered by the
:doc:`close_edit_form `
method of the item.
The ``item`` parameter is the item that triggered the event.
See also
========
:doc:`Forms `
:doc:`close_edit_form `
====================
on_edit_form_created
====================
.. js:function:: on_edit_form_created(item)
**domain**: client
**language**: javascript
**class** :doc:`Group class `
Description
===========
The ``on_edit_form_created`` event is triggered by the
:doc:`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
:doc:`create_edit_form `
method has been called.
See also
========
:doc:`Forms `
:doc:`create_edit_form `
====================
on_edit_form_keydown
====================
on_edit_form_keydown(item, event)
**domain**: client
**language**: javascript
**class** :doc:`Group class `
Description
===========
The ``on_edit_form_keydown`` event is triggered when the keydown event
occurs for the
:doc:`edit_form `
of the item.
The ``item`` parameter is the item that triggered the event.
The ``event`` is JQuery event object.
See also
========
:doc:`Forms `
:doc:`create_edit_form `
==================
on_edit_form_keyup
==================
on_edit_form_keyup(item, event)
**domain**: client
**language**: javascript
**class** :doc:`Group class `
Description
===========
The ``on_edit_form_keyup`` event is triggered when the keyup event
occurs for the
:doc:`edit_form `
of the item.
The ``item`` parameter is the item that triggered the event.
The ``event`` is JQuery event object.
See also
========
:doc:`Forms `
:doc:`create_edit_form `
==================
on_edit_form_shown
==================
.. js:function:: on_edit_form_shown(item)
**domain**: client
**language**: javascript
**class** :doc:`Group class `
Description
===========
The ``on_edit_form_shown`` event is triggered by the
:doc:`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
:doc:`create_edit_form `
method has been called.
See also
========
:doc:`Forms `
:doc:`create_edit_form `
==========================
on_filter_form_close_query
==========================
.. js:function:: on_filter_form_close_query(item)
**domain**: client
**language**: javascript
**class** :doc:`Group class `
Description
===========
The ``on_filter_form_close_query`` event is triggered by the
:doc:`close_filter_form `
method of the item.
The ``item`` parameter is the item that triggered the event.
See also
========
:doc:`Forms `
:doc:`close_filter_form `
======================
on_filter_form_created
======================
.. js:function:: on_filter_form_created(item)
**domain**: client
**language**: javascript
**class** :doc:`Group class `
Description
===========
The ``on_filter_form_created`` event is triggered by the
:doc:`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
:doc:`create_filter_form `
method has been called.
See also
========
:doc:`Forms `
:doc:`create_filter_form `
====================
on_filter_form_shown
====================
.. js:function:: on_filter_form_shown(item)
**domain**: client
**language**: javascript
**class** :doc:`Group class `
Description
===========
The ``on_filter_form_shown`` event is triggered by the
:doc:`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
:doc:`create_filter_form `
method has been called.
See also
========
:doc:`Forms `
:doc:`create_filter_form `
========================
on_view_form_close_query
========================
.. js:function:: on_view_form_close_query(item)
**domain**: client
**language**: javascript
**class** :doc:`Group class `
Description
===========
The ``on_view_form_close_query`` event is triggered by the
:doc:`close_view_form `
method of the item.
The ``item`` parameter is the item that triggered the event.
See also
========
:doc:`Forms `
:doc:`close_view_form `
====================
on_view_form_created
====================
.. js:function:: on_view_form_created(item)
**domain**: client
**language**: javascript
**class** :doc:`Group class `
Description
===========
The ``on_view_form_created`` event is triggered by the
:doc:`view `
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
:doc:`view `
method has been called.
See also
========
:doc:`Forms `
:doc:`view `
====================
on_view_form_keydown
====================
on_view_form_keydown(item, event)
**domain**: client
**language**: javascript
**class** :doc:`Group class `
Description
===========
The ``on_view_form_keydown`` event is triggered when the keydown event
occurs for the
:doc:`view_form `
of the item.
The ``item`` parameter is the item that triggered the event.
The ``event`` is JQuery event object.
See also
========
:doc:`Forms `
:doc:`view `
==================
on_view_form_keyup
==================
on_view_form_keyup(item, event)
**domain**: client
**language**: javascript
**class** :doc:`Group class `
Description
===========
The ``on_view_form_keyup`` event is triggered when the keyup event
occurs for the
:doc:`view_form `
of the item.
The ``item`` parameter is the item that triggered the event.
The ``event`` is JQuery event object.
See also
========
:doc:`Forms `
:doc:`view `
==================
on_view_form_shown
==================
.. js:function:: on_view_form_shown(item)
**domain**: client
**language**: javascript
**class** :doc:`Group class `
Description
===========
The ``on_view_form_shown`` event is triggered by the
:doc:`view `
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
:doc:`view `
method has been called.
See also
========
:doc:`Forms `
:doc:`view `
==========
Item class
==========
.. js:class:: Item
**domain**: client
**language**: javascript
Item class is used to create item objects of the
:doc:`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
:doc:`AbstractItem class `
Attributes and properties
=========================
.. toctree::
:maxdepth: 1
:glob:
item/at_*
Methods
=======
.. toctree::
:maxdepth: 1
:glob:
item/m_*
item/master_*
Events
======
.. toctree::
:maxdepth: 1
:glob:
item/on_*
item/process_*
======
active
======
.. js:attribute:: active
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Specifies whether or not an item dataset is open.
Use ``active`` read only property to determine whether an item dataset is open.
The
:doc:`open `
method changes the value of ``active`` to ``true``. The
:doc:`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.
See also
========
:doc:`Dataset `
:doc:`Navigating datasets `
:doc:`Modifying datasets `
==========
can_modify
==========
.. js:attribute:: active
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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
:doc:`can_create `,
:doc:`can_edit `,
:doc:`can_delete `
methods return false.
By default the ``can_modify`` property is true.
=======
details
=======
.. js:attribute:: details
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Lists all
:doc:`detail `
objects of the item.
See also
========
:doc:`Details `
:doc:`each_detail `
=========
edit_form
=========
.. js:attribute:: edit_form
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Use ``edit_form`` attribute to get access to a Jquery object representing the
edit form of the item.
It is created by the
:doc:`create_edit_form `
method.
The
:doc:`close_edit_form `
method sets the ``edit_form`` value to undefined.
Example
=======
In the following example the button defined in the item edit html template is
assigned a click event:
.. code-block:: js
item.edit_form.find("#ok-btn").on('click.task',
function() {
item.apply_record();
}
);
See also
========
:doc:`Forms `
:doc:`create_edit_form `
:doc:`close_edit_form `
============
edit_options
============
.. js:attribute:: edit_options
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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
:doc:`Edit Form Dialog `
in Application Builder.
You can change ``edit options`` in the
:doc:`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
:doc:`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
:doc:`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
:doc:`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 displayed in the view form,
if not specified the height of the detail table is 200px
fields specify the list of field names that the
:doc:`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
:doc:`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
=================== ============================================================
Example
=======
.. code-block:: js
function on_edit_form_created(item) {
item.edit_options.width = 800;
item.edit_options.close_on_escape = false;
}
See also
========
:doc:`Forms `
:doc:`create_edit_form `
:doc:`close_edit_form `
======
fields
======
.. js:attribute:: fields
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Lists all
:doc:`field `
objects of the item.
Example
=======
.. code-block:: js
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);
}
}
See also
========
:doc:`Fields `
:doc:`Field class `
:doc:`each_field `
===========
filter_form
===========
.. js:attribute:: filter_form
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Use ``filter_form`` attribute to get access to a Jquery object representing the
filter form of the item.
It is created by the
:doc:`create_filter_form `
method.
The
:doc:`close_filter_form `
method sets the ``filter_form`` value to undefined.
Example
=======
In the following example the button defined in the item filter html template is
assigned a click event:
.. code-block:: js
item.filter_form.find("#cancel-btn").on('click',
function() {
item.close_filter()
}
);
See also
========
:doc:`Forms `
:doc:`create_filter_form `
:doc:`close_filter_form `
==============
filter_options
==============
.. js:attribute:: filter_options
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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 button
* ``close_on_escape`` - if true, pressing on the Escape key will trigger the
:doc:`close_filter_form `
method.
* ``close_focusout`` - if true, the
:doc:`close_filter_form `
method will be called when a form loses focus
* ``template_class`` - if specified, the div with this class will be searched in
the task
:doc:`templates `
attribute and used as a form html template when creating a form
Example
=======
.. code-block:: js
function on_filter_form_created(item) {
item.filter_options.width = 700;
}
See also
========
:doc:`Forms `
:doc:`create_filter_form `
:doc:`close_filter_form `
========
Filtered
========
.. js:attribute:: filtered
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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
:doc:`on_filter_record `
event handler, set ``filtered`` to ``true``.
See also
========
:doc:`on_filter_record `
=======
filters
=======
.. js:attribute:: filters
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Lists all
:doc:`filter `
objects of the item.
Example
=======
.. code-block:: js
function invoices_filters(invoices) {
for (var i = 0; i < invoices.filters.length; i++) {
console.log(invoices.filters[i].filter_caption, invoices.filters[i].value);
}
}
See also
========
:doc:`Filters `
:doc:`Filter class `
:doc:`each_filter `
==========
item_state
==========
.. js:attribute:: item_state
**domain**: client
**language**: javascript
**class** :doc:`Item `
Description
===========
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
:doc:`edit `
to put an item into edit state, or call
:doc:`insert `
or
:doc:`append `
to put an item into
insert state.
Posting or cancelling 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:
* :doc:`is_new ` - indicates whether the item is in insert state
* :doc:`is_edited ` - indicates whether the item is in edit state
* :doc:`is_changing ` - indicates whether the item is in edit or
insert state
item_state value can be:
* 0 - inactive state,
* 1 - browse state,
* 2 - insert state,
* 3 - edit state,
* 4 - delete state
item :doc:`task ` attribute have consts object
that defines following attributes:
* "STATE_INACTIVE": 0,
* "STATE_BROWSE": 1,
* "STATE_INSERT": 2,
* "STATE_EDIT": 3,
* "STATE_DELETE": 4
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()
See also
========
:doc:`Modifying datasets `
===========
log_changes
===========
.. js:attribute:: log_changes
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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
:doc:`apply `
method. When ``log_changes`` is false, data changes are not recorded and cannot
be applied to an application server.
See also
========
:doc:`Modifying datasets `
:doc:`apply `
============
lookup_field
============
.. js:attribute:: lookup_field
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Use ``lookup_field`` to check if the item was created to select a value for
the lookup field. See :doc:`Lookup fields `
Example
=======
.. code-block:: js
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
========
.. js:attribute:: paginate
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
The ``paginate`` attribute determines the behavior of a table created by the
:doc:`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
:doc:`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.
See also
========
:doc:`create_table `
:doc:`open `
===========
permissions
===========
.. js:attribute:: permissions
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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:
* can_create
* can_edit
* can_delete
By default theses attributes are set to true.
When these attributes are set to false the corresponding
* :doc:`can_create `,
* :doc:`can_edit `,
* :doc:`can_delete `
methods return false.
See also
========
:doc:`How to prohibit changing record `
=========
read_only
=========
.. js:attribute:: read_only
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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
:doc:`read_only `
property of each field to that value.
If the user role prohibits editing of the record, ``read_only`` always returns ``true``.
See also
========
:doc:`read_only `
Example
=======
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.
.. code-block:: js
function on_edit_form_created(item) {
item.read_only = true;
item.customer.read_only = false;
item.invoice_table.read_only = false;
}
=========
rec_count
=========
.. js:attribute:: rec_count
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Read the ``rec_count`` property to get the number of records owned by
the item's dataset.
If the module declares an
:doc:`on_filter_record `
event handler and the
:doc:`Filtered `
attribute is set, this property calculates the
number of records that satisfy this filter, otherwise the
:doc:`record_count `
method is used to calculate the number of records.
See also
========
:doc:`record_count `
Example
=======
.. code-block:: js
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
======
.. js:attribute:: rec_no
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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.
Example
=======
.. code-block:: js
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;
}
}
}
See also
========
:doc:`Dataset `
:doc:`Navigating datasets `
==========
selections
==========
.. js:attribute:: selections
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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
:doc:`View Form Dialog ` or
multiselect attribute of the
:doc:`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`` attribute changes.
It can also be changed programmatically by using ``add`` or ``remove`` methods
or assigning an array.
Example
=======
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
:doc:`server `
method
.. code-block:: js
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
:doc:`open `
method
.. code-block:: py
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
=============
.. js:attribute:: table_options
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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
:doc:`create_table `
method if its options parameter don't override corresponding option.
These options are set in the **Layout** tab of the
:doc:`View Form Dialog `
in Application Builder.
You can change ``table_options`` in the
:doc:`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
:doc:`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 calculated 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
:doc:`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
:doc:`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 careful - the
item passed to the function can be not item itself, but its
clone that share the same dataset.
=================== ============================================================
Example
=======
.. code-block:: js
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:
.. code-block:: js
item.invoice_table.create_table(item.view_form.find('.view-detail'), {
height: 200,
summary_fields: ['date', 'total'],
});
.. code-block:: js
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'));
See also
========
:doc:`View Form Dialog `
:doc:`on_view_form_created `
:doc:`create_table `
=========
view_form
=========
.. js:attribute:: view_form
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Use ``view_form`` attribute to get access to a Jquery object representing the
view form of the item.
It is created by the
:doc:`view `
method.
The
:doc:`close_view_form `
method sets the ``view_form`` value to undefined.
Example
=======
In the following example the button defined in the item html template is assigned
a click event:
.. code-block:: js
item.view_form.find("#new-btn").on('click',
function() {
item.insert_record();
}
);
See also
========
:doc:`Forms `
:doc:`view `
:doc:`create_view_form `
:doc:`close_view_form `
============
view_options
============
.. js:attribute:: view_options
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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
:doc:`View Form Dialog `
in Application Builder.
You can change view options in the
:doc:`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
:doc:`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
:doc:`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
:doc:`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 displayed 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
:doc:`templates `
attribute and used as a form html template when creating a
form. This attribute must be set before the form is created
=================== ============================================================
Example
=======
.. code-block:: js
function on_view_form_created(item) {
item.view_options.width = 800;
item.view_options.close_button = false;
item.view_options.close_on_escape = false;
}
See also
========
:doc:`Forms `
:doc:`view `
=============
virtual_table
=============
.. js:attribute:: virtual_table
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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
:doc:`open `
method creates an empty data set, and calling the
:doc:`apply `
method does nothing.
===============
add_edit_button
===============
.. js:function:: add_edit_button(text, options)
**domain**: client
**language**: javascript
Description
===========
Use ``add_edit_button`` to dynamically add a button in the edit form.
This method have the same parameters as the
:doc:`add_view_button `
method
===============
add_view_button
===============
.. js:function:: add_view_button(text, options)
**domain**: client
**language**: javascript
Description
===========
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 button
* ``options`` - options that specify additional properties of the button
The ``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 button
* ``btn_class`` - the class of the button
* ``type`` - specifies the type (color) of the button, it can be one of the
following text values:
* primary
* success
* info
* warning
* danger
* ``image`` - an icon class, one of the icons by Glyphicons from http://getbootstrap.com/2.3.2/base-css.html
* ``secondary``: if this attribute is set to true, the button will be right aligned if
**Buttons on top** attribute of the
:doc:`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 true
The method returns a JQuery object of the button.
Examples
========
.. code-block:: js
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
======
.. js:function:: append()
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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
:doc:`post `
method, and then apply them to the item database table, using
:doc:`apply `
method.
The ``append`` method
* checks if item dataset is
:doc:`active `
, otherwise raises exception
* if the item is a
:doc:`detail `
, checks if the master item is in edit or insert
:doc:`state `
, otherwise raises exception
* if the item is not a
:doc:`detail `
checks if it is in browse
:doc:`state `
, otherwise raises exception
* triggers the
:doc:`on_before_append `
event handler if one is defined for the item
* open a new, empty record at the end of the dataset
* puts the item into insert
:doc:`state `
* triggers the
:doc:`on_after_append `
event handler if one is defined for the item.
* updates
:doc:`data-aware controls `
See also
========
:doc:`Modifying datasets `
=============
append_record
=============
.. js:function:: append_record(container)
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Open a new, empty record at the end of the dataset and creates an
:doc:`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
:doc:`Edit Form Dialog ` or modeless attribute
of the
:doc:`edit_options ` is set programmatically and task has the
:doc:`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
:doc:`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
:doc:`copy `
method to create a copy of the item. This copy will be used to append the record.
The ``append_record`` method
* calls the
:doc:`can_create `
method to check whether a user have a right to append a record, and if not,
returns
* checks whether the item is in edit or insert
:doc:`state `
, and if not, calls the
:doc:`append `
method to append a record
* calls the
:doc:`create_edit_form `
method to create a form for visuall editing of the record
See also
========
:doc:`Modifying datasets `
:doc:`append `
:doc:`can_create `
=====
apply
=====
.. js:function:: apply(callback, params, async)
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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 executed
* ``params`` - an object specifying user defined params, that can be used
on the server in the
:doc:`on_apply `
event handler for some additional processing
* ``async``: if its value is true, and callback parameter is missing, the request
is executed asynchronously
The order of parameters doesn't matter.
The ``apply`` method
* checks whether the item is a detail, and if it is, returns (the master saves the
details changes)
* checks whether the item is in edit or insert
:doc:`state `
, and if so, posts the record
* checks if the change log has changes, and if not, executes callback if it is
passed and then returns
* triggers the
:doc:`on_before_apply `
event handler if one is defined for the item
* sends changes to the server
* server on receiving the request checks whether
:doc:`on_apply `
event handler is defined for the item, and if it is, executes it, otherwise
generates and executes SQL query to write changes to the database, see also
:doc:`on_apply events ` topic
* when generating an SQL query, checks whether a user, that send the request, has
rights to make these changes, if not raises an exception
* writes changes to the database
* after writing changes to the database, server sends to the client results
of the execution
* if exception was raised during the operation on the server the client throws an
exception, before throwing exception, if the callback parameter is passed,
it is called and the error is passed as the callback function parameter
* the client, based on the results, updates the change log
* triggers the
:doc:`on_after_apply `
event handler if one is defined for the item
* if the callback parameter is passed, it is called.
.. 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
:doc:`on_apply `
event handler, these changes will not be reflected on the client. You can
update them yourself using, for example,
:doc:`refresh_record `
method
Example
=======
.. code-block:: js
var self = this;
this.apply(function(err) {
if (err) {
self.alert_error(err);
}
else {
//some code to execute after appling changes
}
});
See also
========
:doc:`Modifying datasets `
============
apply_record
============
.. js:function:: apply_record()
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Writes changes to the application dataset.
The ``apply_record`` method
* calls the
:doc:`apply `
to writes changes to the dataset.
* calls the
:doc:`close_edit_form `
method to destroy the edit_form
See also
========
:doc:`Modifying datasets `
:doc:`close_edit_form `
:doc:`apply `
==============
assign_filters
==============
.. js:function:: assign_filters(item)
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Use ``assign_filters`` to set filter values of the item to values of filters of
the ``item`` parameter.
Example
=======
.. code-block:: js
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);
});
}
);
}
See also
========
:doc:`Filtering records `
:doc:`Filters `
===
bof
===
.. js:function:: bof()
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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
* Opens an item dataset.
* Calls an item's :doc:`first ` method.
* Call an item's :doc:`prior ` method, and the method fails (because
the cursor is already on the first row in the dataset).
bof returns false in all other cases.
.. note::
If both :doc:`eof ` and bof return true, the item dataset is empty.
See also
========
:doc:`Dataset `
:doc:`Navigating datasets `
============
calc_summary
============
.. js:function:: calc_summary(detail, fields)
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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.
Example
=======
.. code-block:: js
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);
}
See also
========
:doc:`on_detail_changed `
:doc:`Details `
==========
can_create
==========
.. js:function:: can_create()
**domain**: client
**language**: javascript
**class** :doc:`Item `
Description
===========
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 :doc:`roles node `
in the Application Builder when the project
:doc:`safe mode parameter `
is set as well as the values of the
:doc:`permissions `
attribute and the value of
:doc:`can_modify ` attribute.
Example
=======
.. code-block:: js
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);
}
See also
========
:doc:`Parameters `
==========
can_delete
==========
.. js:function:: can_delete()
**domain**: client
**language**: javascript
**class** :doc:`Item `
Description
===========
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 :doc:`roles node `
in the Application Builder when the project
:doc:`safe mode parameter `
is set as well as the values of the
:doc:`permissions `
attribute and the value of
:doc:`can_modify ` attribute.
Example
=======
.. code-block:: js
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
========
.. js:function:: can_edit()
**domain**: client
**language**: javascript
**class** :doc:`Item `
Description
===========
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 :doc:`roles node `
in the Application Builder when the project
:doc:`safe mode parameter `
is set as well as the values of the
:doc:`permissions `
attribute and the value of
:doc:`can_modify ` attribute.
Example
=======
.. code-block:: js
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
======
.. js:function:: cancel()
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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**
* triggers the
:doc:`on_before_cancel `
event handler if one is defined for the item.
* to undo modifications made to the current record and its details if the record
has been edited or removes the new record if one was appended or inserted.
* puts the item into browse :doc:`state `
* triggers the
:doc:`on_after_cancel `
event handler if one is
defined for the item.
* updates
:doc:`data-aware controls `
See also
========
:doc:`Modifying datasets `
===========
cancel_edit
===========
.. js:function:: cancel_edit()
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Cancel visual editing on the record
The ``cancel_edit`` method
* calls the
:doc:`close_edit_form `
method to destroy the edit_form
* calls the
:doc:`cancel `
method to undo modifications made to the record
See also
========
:doc:`Modifying datasets `
:doc:`close_edit_form `
:doc:`cancel `
=============
clear_filters
=============
.. js:function:: clear_filters()
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Use ``clear_filters`` to set filter values of the item to ``null``.
See also
========
:doc:`Filtering records `
:doc:`Filters `
=====
clone
=====
.. js:function:: clone(keep_filtered)
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
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.
Example
=======
.. code-block:: js
function calc_sum(item) {
var clone = item.clone(),
result = 0;
clone.each(function(c) {
result += c.sum.value;
})
return result;
}
See also
========
:doc:`on_filter_record `
=====
close
=====
.. js:function:: close()
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Call ``сlose`` to close an item dataset. After dataset is closed the
:doc:`active `
property is ``false``.
See also
========
:doc:`Dataset `
:doc:`open `
===============
close_edit_form
===============
.. js:function:: close_edit_form()
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
The ``close_edit_form`` method triggers the
:doc:`on_edit_form_close_query `
event handler of the item, if defined:
* If the handler returns ``true``, the form is destroyed, the item's
:attr:`edit_form` attribute is set to ``undefined``, and the method exits.
* If the handler returns ``false``, the operation is aborted and the method exits.
If the handler returns ``undefined``, the method triggers the
:doc:`on_edit_form_close_query `
of the item's parent group, if defined:
* If the handler returns ``true``, the form is destroyed, the item's
:attr:`edit_form` attribute is set to ``undefined``, and the method exits.
* If the handler returns ``false``, the operation is aborted and the method exits.
If the group handler returns ``undefined``, the method triggers the
:doc:`on_edit_form_close_query `
of the task:
* If the handler returns ``true``, the form is destroyed, the item's
:attr:`edit_form` attribute is set to ``undefined``, and the method exits.
* If the handler returns ``false``, the operation is aborted and the method exits.
If no event handler is defined, or none of the handlers return ``false``,
the form is destroyed and the item's :attr:`edit_form` attribute is set to
``undefined``.
``close_edit_form`` is mostly used with :doc:`Virtual table `.
Example:
Form 2 has a button ``Next Form`` and after click, the form is destroyed and Form 1 displayed.
.. code:: js
function on_edit_form_created(item) {
item.edit_form.find('#ok-btn')
.text('Next Form')
.off('click.task')
.on('click', function() {
item.close_edit_form();
setTimeout(function() {
show_f1(item);
}, 300);
});
}
function on_edit_form_close_query(item) {
return true;
}
See also
========
:doc:`Virtual Table `
:doc:`Forms `
:doc:`create_edit_form `
:doc:`edit_form `
:doc:`on_edit_form_close_query `
=================
close_filter_form
=================
.. js:function:: close_filter_form()
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Use the ``close_filter_form`` method to close the item's filter form.
The ``close_filter_form`` method triggers the
:doc:`on_filter_form_close_query `
event handler of the item, if defined:
* If the handler returns ``true``, the form is destroyed, the item's
:attr:`filter_form` attribute is set to ``undefined``, and the method exits.
* If the handler returns ``false``, the operation is aborted and the method exits.
If the handler returns ``undefined``, the method triggers the
:doc:`on_filter_form_close_query `
of the item's parent group, if defined:
* If the handler returns ``true``, the form is destroyed, the item's
:attr:`filter_form` attribute is set to ``undefined``, and the method exits.
* If the handler returns ``false``, the operation is aborted and the method exits.
If the group handler returns ``undefined``, the method triggers the
:doc:`on_filter_form_close_query `
of the task:
* If the handler returns ``true``, the form is destroyed, the item's
:attr:`filter_form` attribute is set to ``undefined``, and the method exits.
* If the handler returns ``false``, the operation is aborted and the method exits.
If no event handler is defined, or none of the handlers return ``false``,
the form is destroyed and the item's :attr:`filter_form` attribute is set to
``undefined``.
See also
========
:doc:`Forms `
:doc:`create_filter_form `
:doc:`filter_form `
===============
close_view_form
===============
.. js:function:: close_view_form()
**domain**: client
**language**: javascript
**class** :doc:`Item class `
Description
===========
Use ``close_view_form`` method to close the view form of the item.
The ``close_view_form`` method triggers the
:doc:`on_view_form_close_query `
event handler of the item, if one is defined. If the event handler is defined
and
* returns ``true`` - the form is destroyed, the item's
:doc:`view_form `
attribute is set to undefined and the methods exits
* return ``false`` - the operation is aborted and the methods exits,
If it don't return a value (undefined) the method triggers the
:doc:`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
* returns ``true`` - the form is destroyed, the item's
:doc:`view_form `
attribute is set to undefined and the methods exits
* return ``false`` - the operation is aborted and the methods exits,
If it don't return a value (undefined) the method triggers the
:doc:`on_view_form_close_query `
of the task. If this
event handler is defined and
* returns ``true`` - the form is destroyed, the item's
:doc:`view_form `
attribute is set to undefined and the methods exits
* return ``false`` - 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
:doc:`view_form `
attribute is set to undefined.
See also
========
:doc:`Forms `
:doc:`view `
:doc:`view_form