CuneiTable

จาก คูนิฟ็อกซ์ วิกิ

CuneiTable is designed specifically to enable in-line data entry. Design-wise, it is more limited than CuneiForm since design flexibility is not highly expected from table-rype objects. In essence, CuneiTable is a wrapper for the accompanying CuneiForm, whether that form be the in-line type or the full expansion type.

Define

Defining a CuneiTable is essentially declaring a CuneiForm with a few additional class variables. Below is an example of a very basic CuneiTable:

class AccCodeTable(CuneiTable):
    MASTER_colsizes = [10, 20, 50, 20]
    MASTER_colnames = ["StID", "Account Code", "Account Name", "Account Group"]
    MASTER_coltypes = ["int", "str", "str", "sel"]
    MASTER_choicecols = ["accgrp"]
    MASTER_choices = [[
                         ("1", "Asset"),
                         ("2", "Liability"),
                         ("3", "Equity"),
                         ("4", "Revenue"),
                         ("5", "Expense")
                     ]]
    MASTER_firstonly = ["stid"]
    class SubForm(FlaskForm, CuneiSubForm):
        stid = StringField("StID")
        code = StringField("Code", validators=[InputRequired("Required!")])
        name = StringField("Name", validators=[InputRequired("Required!")])
        accgrp = SelectField("Group", default=1)
        def validate_stid(self, stid):
            # Validation goes here

The subclass SubForm is defined like the stripped down version of CuneiForm. Only put fields and validation logics here. All other behaviours are controlled by the MASTER variables from the CuneiTable level.

NOTE THAT neither of the Submit-delete Field Set (submit, is_del, and del_submit) is declared in the SubForm. These fields are taken care of automatically during CuneiTable initiation.

The Holy Trinity of CuneiTable

The 3 MASTER variables listed below are required for all CuneiTable classes.

  • MASTER_colnames ([str,]): The list of column headers to appear on the page.
  • MASTER_coltypes ([str,]): The list of column value types. Available types include:
    • All types listed in the main article for CuneiForm.
    • 'str': Normal string value.
    • 'check': Value represented by a checkbox.
    • 'sel': Value with multiple choices. In other words, this is equivalent to a drop-list field in forms. In the same fashion, the displayed values for this type is not exactly the same as the values stored in the database. The raw value must pass through a map first before being displayed.
    • 'rad': Similar to 'sel', but this value type is represented by radio buttons in multiple sub-columns instead.
    • 'submit', 'del_submit': Submit button. (Not handled manually, they are handled automatically during initiation.)
  • MASTER_colsizes ([float/int,]): The list of relative column sizes. CuneiFox will calculate the display sizes automatically.

NOTE #1: The member order in each of these lists follow the definition order of fields in the SubForm. DO CHECK that all of these 3 lists and the SubForm fields share the same length.

NOTE #2: On the client-side, a radio field ('rad') is displayed as multiple sub-columns (one for each choice). Hence:

  • The display headers of sub-columns are not dictated by the corresponding value in MASTER_colnames, but by the values in MASTER_radios (Detailed in a sub-section below) instead.
  • The corresponding member in the MASTER_colsizes list is NOT merely one numerical value, BUT a list of numerical values instead.

Choices and Radios

Columns with limited choices of predesignated values should utilize 'choice' or 'radio' representation; especially when the stored values must be mapped to another set of more readable or localizable display values. Ideally:

  • Columns utilizing choice representation is typed 'sel' (MASTER_coltypes[i] = 'sel'), and the corresponding field in the SubForm is a SelectField.
  • Columns utilizing radio representation is typed 'rad' (MASTER_coltypes[i] = 'rad'), and the corresponding field in the SubForm is a RadioField.
MASTER_choicecols = [str choice_col0, str choice_col1, ...]
MASTER_choices    = [
                        [ (str col0_choice0, str col0_display0),
                          (str col0_choice1, str col0_display1),
                          ... ],
                        [ (str col1_choice0, str col1_display0),
                          (str col1_choice1, str col1_display1),
                          ... ],
                        ...
                    ]

MASTER_radiocols  = [str choice_col0, str choice_col1, ...]
MASTER_radios     = [
                        [ (str col0_choice0, str col0_display0),
                          (str col0_choice1, str col0_display1),
                          ... ],
                        [ (str col1_choice0, str col1_display0),
                          (str col1_choice1, str col1_display1),
                          ... ],
                        ...
                    ]

An example can be seen in the code at the top of the article.

NOTE #1: Take care that MASTER_choicecols and MASTER_choices have the same length. Similarly for MASTER_radiocols and MASTER_radios.

NOTE #2: The SelectField and RadioField defined in the SubForm DOES NOT include the choices. (Again, see example at the top.)

Search and Fill

Since the in-line form of a table is programmatically generated, the search-fill function cannot wait until the designing step like CuneiForm's does. The search-fill function of an in-line form is defined in this step via the variable MASTER_schfill.

MASTER_schfill = {
                     str search_col0: [str search_spec0, str fill_spec0],
                     str search_col1: [str search_spec1, str fill_spec1],
                     ...
                 }

# Example from Bank Account table
MASTER_schfill = {
                     "bank_code":    ["ModalBank:code",
                                      "bank_stid:stid,bank_name:name,bank_branch:branch"],
                     "acccode_code": ["ModalAccCode:code",
                                      "acccode_name:name,acccode_stid:stid"]
                 }

NOTE: The formats of search_specX and fill_specX is similar to arguments search and fill in CuneiForm's render_blank macro.

Instant Calculation

Instant calculation function within in-line forms, or 'instacalc', allows a change in one form field to initiate a small calculation request to the server and update other affected fields based on the calculation result. It is specified in the variable MASTER_instacalc of the CuneiTable.

(Refer to the dedicated sub-section in CuneiForm article for format and example.)

Auto-numbering Function

In-line forms with auto-numbering specifications work with CuneiFox core system to serve auto running numbers in a particular format (usually for document numbers). The specifications for this feature are embedded within MASTER_autorun of the CuneiTable.

(Refer to the dedicated sub-section in CuneiForm article for format and example.)

Table with a Separate Form Modal

Sometimes, the entries have either too many columns or columns not suitable for display in the table format. In such cases, developers can use the full-form option. CuneiTable with an associated full-form is only defined with essential columns for a 'quick glance' view. The full form is shown in a pop-up modal for entry addition/edit.

A CuneiTable can be linked to a full-form via the class variable MASTER_fullform which takes the following format:

MASTER_fullform = [str module_code, str fullform_class_name,
                   str fullform_modal_header_text, str modal_size]

# Example from Product table
MASTER_fullform = ['cunei_iv', 'ProductFullForm', 'Product Information', 'xl']
  • size dictates the modal size displayed on the client-side. It utilizes BootStrap 4's 'modal-<size>' classes. Available choices are 'sm', 'md' (corresponds to default modal size), 'lg', and 'xl'.

NOTES on full-form class and macro:

  • The CuneiForm class fullform_class_name must be found under module_code in the file cuneifox/<module_code>/forms.py or cuneifox/<module_code>/forms.pyc.
  • The definition of the full-form class DOES NOT include the Submit-delete Field Set
  • The Jinja2 macro for the full-form design must also be named fullform_class_name and must be found under module_code in the file cuneifox/<module_code>/template/<module_code>/form_macro.html.
  • Refer to the main CuneiForm article for more details on form definition and design. For information specific the full-form macros, refer to a dedicated sub-section below.

In-line Form with Expansion

Some tables contain just a tab too many columns, but not enough to warrant a fully-fledged separate form. In this case, CuneiTable allows its in-line form to have some fields drawn separately in an expansion modal. This behaviour is controlled by the variables MASTER_expand, MASTER_tbexpand, and MASTER_manualdraw.

MASTER_expand = {str trigger_field0: str expand_spec0,
                 str trigger_field1: str expand_spec1,
                 ...}
MASTER_tbexpand = str expand_spec
MASTER_manualdraw = [str expand_field0, str expand_field1, ...]

# Example from the product table on the 'Product Delivery' page
MASTER_expand = {"product_code_mock":"BodyEx1:product_name"}
MASTER_tbexpand = "BodyEx1:product_name"
MASTER_manualdraw = ["product_code", "product_name", "desc", "manage_serial", "serial_nos"]
  • trigger_field: The field with an Expand button '<expand_modal_id>:<first_expand_field>' attached when the form is triggered.
  • expand_spec: The modal and field to focus on once the Expand button is presses. (Shares the format with the argument expand in CuneiForm's render_blank macro.)
  • expand_field: The field to be skipped during dynamic in-line form generation. Fields listed in MASTER_manualdraw must be put on the page manually.

Injections & Post-submission Logics

Some columns, while not stored directly in the database, are quite essential, or at least useful, to display on the client-side table. In CuneiFox's normal operation, whether it be a data fetch request or a display table update after a database commission, the server returns (representation of) entries from within the database. This ensures the client-side is shown the most updated data, but it also means that columns not in the database do not show up by default. Variables MASTER_preserve_across and MASTER_inject_func seek to better this behaviour.

Employing a similar routine, variable MASTER_post_submit_func allows additional logic to be run after a database commit from a CuneiTable.

  • MASTER_preserve_across: (This feature is active AFTER SUBMISSION) A list of field names, present in the in-line form but not in the database, whose values are read from the form and reinjected into the data sent to client for table manipulation.
  • MASTER_inject_func: (This feature is active ON SINGLE-ENTRY FETCH) A list of functions (and their arguments) to run just before the fetched entry is sent to the client-side.
  • MASTER_post_submit_func: (This feature is active AFTER SUBMISSION) A list of functions (and their arguments) to run just after a database commit. This feature is useful both for additional logic run and to modify the returned entry (in the same vein as inject_func does for a fetch request).
MASTER_preserve_across = [str field_name0, str field_name1, ...]
MASTER_inject_func =      {'func': <function_object>,
                           'args': <list_of_ordered_arguments>}
MASTER_post_submit_func = {'func': <function_object>,
                           'args': <list_of_ordered_arguments>}

# Example from (former) tax ID table
MASTER_preserve_across = ["bcount"]
# Example from product group table
MASTER_inject_func = {"func":defvat_inject, "args":[]}
# Example from account book table
MASTER_post_submit_func = {"func":accbook_to_docseries, "args":[]}

NOTE #1: preserve_across feature is automatically assumed for columns covered by CuneiModel's Cross-table references. There is no need to list such columns in MASTER_preserve_across.

NOTE #2: At the time of writing, CuneiTable's injections & post-submission logics are handled automatically only on the #Shortcut Route for Single-table Page & Search Table.

Other Table Definition Variables

Other notable MASTER variables that can be set upon form definition include:

  • MASTER_firstonly: A list of field names that are only active (editable) for a new entry, but inactive (not editable) when editing an old entry.
  • MASTER_pseudohides: A list of field names for which the corresponding fields are not rendered on form triggering.
  • MASTER_delconfirm: The specification for the delete confirmation modal. This value shares the format with the argument confirm_first in CuneiForm's render_blank macro. The default value is ['This will delete...', 'code']
MASTER_firstonly = [str field_name0, str field_name1, ...]
MASTER_pseudohides = [str field_name0, str field_name1, ...]
MASTER_delconfirm = [str confirm_modal_header, str field_name_to_confirm]

# Example from account code table
MASTER_firstonly = ["stid"]
# Example from journal setting table
MASTER_pseudohides = ["book", "colspec"]
# Example from Accounting Journal's VAT table
MASTER_delconfirm = ["This will delete...", "taxdoc"]

Initiate

CuneiTable class has its own __init__ function.

__init__(self, prefix="", **kwargs)

Parameters
  • prefix (str): This value is reflected in CuneiForm's _id attribute during initiation. It is also used as the basis to name corresponding forms, modals, and other elements.
  • Other keyword arguments: See notes under sections below.

The rough step-by-step of a CuneiTable's initiation is as follows:

  1. Assign the following MASTER variables to corresponding attributes of the CuneiTable instance (if the attributes are not specified otherwise):
  2. Modify the full-form class with standard field set, initiate the full-form, and modify the form instance's attributes (where applicable).
  3. Modify the in-line SubForm class with standard field set, initiate the in-line form, and modify the form instance's attributes.
  4. Assign the choice and radio MASTER variables to corresponding attributes of the CuneiTable instance (if the attributes are not specified otherwise). Then, use the attributes to modify the choices of the in-line form fields.
  5. Add values corresponding to the standard column set to the 3 main MASTERs.

Standard Column Set

CuneiTable requires a few standard columns in order to function properly. These columns need not be defined explicitly, but are programmatically generated and integrated into all CuneiTable instances. The 4 standard columns include:

  • id, submit, is_del: These columns correspond to the #Submit-delete Field Set of the corresponding in-line form or full-form. Developers can also choose to explicitly define these columns in the class definition (especially the id column) if they desire.
  • The multi-selection column: This column is only created for a table with multi-selection enabled. It is displayed as the leftmost column of the table. (See the note on multi attribute below.)

Column Sizes & Post-init Modification

For easier development, column sizes are defined (via MASTER_colsizes) in relative sizes. However, those values are not used directly in the client-side render. They would have to converted to percentage values first via an internal function. While developers rarely need to tamper with column sizes after class definition, there are cases where tables are reused for many pages and with some columns hidden (in other words, resized to 0) in some of its incarnation.

Column resizing, post-initiation, is done by running 2 function consecutively: assign_csize and tune_cols.

assign_csize(self, cname, csize)

Parameters
  • self (CuneiTable instance)
  • cname (str): The name of the column to be resized.
  • csize (int ot float): The new relative sizes.
Notes At this stage, the CuneiTable instance have alrady gone through one round of size tuning. Hence, the value csize should be set to the new relative (100-based) basis.

tune_cols(self, skip_multicheck=False)

Parameters
  • self (CuneiTable instance)
  • skip_multicheck (bool): Whether to skip the column prepend for multi-selection. Always set to True during column resizing.

Notes on Keyword Arguments

The following sub-sections introduce notable keyword arguments. Other keywords not detailed here are also welcome during CuneiTable initiation. Unless explicitly specified, all keyword arguments can be accessed post-initiation as attributes of the created CuneiTable instance.

MASTER-adjacents

Back to the #Define section, we are introduced to a number of MASTER variables. Those variables set at table definition act as the default values for each table type. However, developers can feed an overriding value that applies only to the CuneiTable instance being initiated via a corresponding non-master keyword (e.g. use keyword instacalc to override the class's MASTER_instacalc).

The option to modify object-specific values after initiation is also available. However, since value packing and manipulation occurs during initiation, some details should be noted:

  • colsizes, colnames, expand, tbexpand, manualdraw, del_confirm (corresponds to MASTER_delconfirm), preserve_across, post_submit_func, inject_func, and pseudohides are normal table attributes and directly take on the values of their MASTER counterparts. They can easily be accessed and modified.
  • radiocols and choicecols take on the values of their MASTER counterparts. These 2 attributes work closely with the next item on this list.
  • radios and choices take on the values of their MASTER counterparts. These attributes are used to generate a translation map on the client-side to aid value display. CuneiTable's initiation routine also includes assigning these values as the choices attributes of corresponding in-line form (including expansion) fields as well.
  • schfill takes on the value of MASTER_schfill. This attribute applies only to in-line form (dynamically-generated) fields. Fields under manualdraw must have equivalent values specified during the form designing phase.

The following attributes do not allow value feeding during initiation phase. However, they can be modified after the fact if needed, so it is fit to discuss them here. (SubForm and FullForm referenced here can be accessed via appropriate CuneiTable attributes. See #Form-related Attributes sub-section.)

  • SubForm.firstonly and FullForm.firstonly: For an in-line form, the form attribute is copied directly from MASTER_firstonly. For a full-form, members from MASTER_firstonly are appended to what the form originally has by itself.
  • SubForm.autorun and FullForm.autorun: For an in-line form, the form attribute is copied directly from MASTER_autorun. For a full-form, members from MASTER_autorun are appended to what the form originally has by itself.
  • SubForm.instacalc takes the value of MASTER_instacalc. Its post-init format can be found in the CuneiForm article. Note that MASTER_instacalc only affect in-line form fields.
  • SubForm.coltypes is taken (and modified to accommodate the standard column set) from MASTER_coltypes. The initation routine also includes extracting appropriate values from this list to set the special_type field attributes for in-line form as well.

Know Thy Self

These attributes dictate the way a CuneiTable is drawn on the client-side along with ways to identify its affiliated components:

  • _id (str): The initiation argument 'prefix' repacked. This value will become the HTML id of the table object. (The attribute name is underscored to avoid conflict.)
  • in_modal (str: defaults to False for stand-alone tables): The ID of the modal with this table as the MAIN SUBMITTING COMPONENT, like the search modal affiliated with a search table, for example. (See CuneiModal for details.)
  • bound_modal (str: defaults to False): The ID of the modal in which this table is drawn but IS NOT the MAIN SUBMITTING COMPONENT.
  • slim (bool: defaults to False): Whether to render the in-line form in slim mode. When supplied as an init argument, it is transfered directly to SubForm.subslim. (For CuneiTable's in-line forms, the difference is less obvious than in full-blown forms. Here, slim mode only saves vertical spaces with reduced padding.)
  • linkmode (list: defaults to False): If set, the CuneiTable is not for selection. Each row of the table acts as a navigation link to another page. This attribute take the format ['<url_for_route_str>', ['<argument_col0>', '<argument_col1>', ...]]
  • multi (bool: defaults to False): Whether the table is able to make multiple selections at a time. If that is the case, a special column is automatically set up for this feature (see #Standard Column Set).
  • searchable (bool: defaults to True): Whether to draw a live-search field at the top of the table (see #Live Search).

Editable or Read-only

The attribute editable (bool: defaults to False) dictates whether a CuneiTable is edit-able at all. However, the finer details of which actions are allowed or suppressed are more finely tuned via the following attributes:

  • perm_bit (int: defaults to 1): The permission bit dictating what actions the useris allowed to do. It mimics the integer permission bits in the main CuneiFox system, namely:
    • 0: Not readable. (Usually users with this permission level should be prevented from reaching the page at all. DO NOT rely on this permission bit as a security measure,)
    • 1: Read-only. (Even if the editable attribute is set to True, the user will still find all non-read buttons dimmed out.)
    • 2: Add. (Allows 'add' type actions: add and copy.)
    • 3: Add + Edit.
    • ≥4: Add + Edit + Delete.
  • suppress_add (bool: defaults to False): Whether to suppress the drawing of the table's Add button Add button. (Suitalbe for cases where entries are supposed to be edit-only.)
  • suppress_copy (bool: defaults to True): Whether to suppress the drawing of the table's Copy button Copy button.
  • suppress_del (bool: defaults to False): Whether to suppress the drawing of the table's Delete button Delete button.

Sequence and Columns

  • seq and cnames attributes of a CuneiTable are copied to its in-line form. These attributes do not support value feeding during initiation because they are highly involved in the process. In fact, it is NOT RECOMMENDED to trifle with them at all. Their functions can be found in the corresponding section on the CuneiForm page.
  • content_width (str): The width of the table itself (not the bounding element of the table) on the client-side display. The value can be anything interpretable as CSS size.

Form-related Attributes

Before we go on and make references to the forms themselves, let us take a little detour to see how to control the appearances of table-bound forms on the client-side.

  • An in-line form is not much of a hassle, and user only loses the ability to manipulate entries (the table becomes read-only), but not any information, when it is not drawn. So only the editable discussed above is all we need. The form is only drawn only when table.editable = True.
  • A full-form is more complicated. For example, it might need to be drawn even if the table is read-only because losing it at all means losing the ability to see the full entries'. By default, a full-form is always drawn, the editable attribute only controls the read-only status of the form.
    If the full-form is not wanted at all (for example, when a full-form entails too many search pop-ups that threaten to clutter the user experience), developers have to choices to:
    • Feed the keyword argument suppress_fullform=True at CuneiTable initiation.
    • Use keyword argument skip_fullform=true on BOTH the general_table_block and general_table_tail macros in an HTML template.

Another notable attribute is modal_view (int: 0, 1, or 2). This attribute applies to a full-form. It acts as the flag to control the drawing of the full-form itself and the associated Edit Edit button, View View button and Delete Delete button buttons. Its value is automatically interpreted during initiation and requires NO MANUAL MANIPULATION. Nevertheless, its value might be useful to developers:

  • 0: The table has no full-form defined or the it is suppressed at initiation.
  • 1: The table has an editable full-form and relevant buttons will be drawn. Whether the buttons are usable is determined by the table's perm_bit attribute.
  • 2: The table has a read-only full-form and only the View button is drawn.

With form-control discussed, we are now ready. The following 2 attributes refer to the forms bound to the CuneiTable. These attributes DO NOT support value feeding during initiation.

  • form refers to the table's in-line form.
  • fullform is a list of values related to the table's full-form. When applicable, the attribute reads and repackages MASTER_fullform into the format below:
table.fullform = [str fullform_modal_header,
                  str modal_class,  # OBSOLETE
                  Jinja2_Macro form_design_template,
                  CuneiForm fullform_instance,
                  str modal_size]

# An example for full-form attribute manipulation post-initiation.
product_tb.fullform[3].slim = True

NOTE #1: The FullForm instance itself has a notable attribute modal_addi_btns that dictates additional buttons in the full-form modal (besides the default OK and Cancel buttons). The value is only modifiable post-initiation, as in the example below:

table.fullform[3].modal_addi_btns = \
      [ [str btn_name0, str btn_label0, str_or_list btn_class0, str function_name0], ... ]

# Example from withholding tax invoice table in the accounting journal page.
wht_tb.fullform[3].modal_addi_btns = [["prn", "Print", "dark", "whtprn"]]
  • btn_nameX is used to id the button element on the client-side as '<table._id>_FullFormModal-<btn_name>btn'.
  • btn_labelX is the label appearing on the button.
  • btn_classX is the color scheme of the button. It can take the format of either the class or custom_color arguments of CuneiForm's render_btn macro.
  • function_nameX is the name of the JavaScript function to be called when the button is pressed.

NOTE #2: The obsolete modal_class member in the fullform attribute is a relic of the past when the table and modal headers are limited to preset colours in the BootStrap template. In the current version, the colours can be more freely set, but the full-form modal must shares the same color scheme with the parent table to emphasize their relationship.

Getting Data

Under the normal work flow, CuneiFox sent only blank components to the client first to render the page. At this stage, a CuneiTable only displays as a header with a blank body. Once the page components are loaded, the elements (including CuneiTables) can then fetch data from the server, each using a separate HTML request.

The attributes populate_route, populate_suppress, and populate_id dictate the table's behaviours regarding data request. See explanation on them in CuneiForm#Getting Data.

NOTE #1: CuneiTable supports multiple fetch modes listed below:

  • 'qsch' is for populating the entire table. If the table is a part of a search-fill routine of another form, the table is populated only after the quick-search fails to fetch a unique selection.
  • 'sel' is used to fetch the full database entries for the current selection(s). This mode is triggered when the selection(s) of a search modal is properly committed.
  • 'free_grab' fetches one active entries to populate a table's full-form. This mode is equivalent to the fetch mode 'grab' on stand-alone AND in-line forms. Details on 'grab' mode can be found under NOTES in CuneiForm#Getting Data.

NOTE #2: For a CuneiTable with a full-form, the form's populate_id is automatically assumed to be similar to the table's (with change trigger if the fetch mode 'free_grab' is not suppressed) PLUS the column name 'id'.

Sending Data

Both in-line form and full-form submit in a non-redirecting fashion by CuneiFox default. So, the only important attribute regarding form submission via a CuneiTable is:

  • post_route (url_for_string: defaults to False): Route to send a submit request.

In rare cases where redirection is needed, developers can either modify the appropriate attributes of the in-line form and/or full-form (see CuneiForm#Sending Data), OR package a redirect or reload command with the submission response (see CuneiForm#Non-redirecting Form).

Design & Pre-made Scripts

Now that a CuneiTable is defined, initiated, and sent along with a template, here comes to frontend part. CuneiFox's standard pattern for putting a table up on an HTML page includes:

  1. Design a full-form macro. (Only when applicable. Done once and reuse for all repeated instances.)
  2. Initiate a <table> DOM element and a Javascript table object. Both fall under the scope of the Jinja2 macro general_table_block.
  3. Design the pop-up modal for the extension (manually drawn) parts of the in-line form. (Only when applicable. See CuneiModal#Design & Pre-made Scripts for details.)
  4. Finalize the Javascript object and bind relevant events with the Jinja2 macro genetal_table_tail.

One can see that, for the simplest tables, only two lines of macros is sufficient.

/* Full-form macro lives elsewhere and not shown here. */
{{ CuneiTables.general_table_block(table, block_height, ...) }}
    /* If in-line form has an extension, the extension modal goes here */
    {{ CuneiModals.modal_upper_half('<ext_modal_name>', ...) }}
    ...
    ...
    {{ CuneiModals.modal_lower_half('ext_modal_name', ...) }}
    /* End of form extension block */
{{ CuneiTables.general_table_tail(table, ...) }}

Full-form Macro

The macro of a CuneiTables' Full Form is to be written in the file cuneifox/<module_code>/templates/<module_code>/form_macro.html directory. In essence, each full-form macro is merely a CuneiForm design with a few notable properties.

  1. The Jinja2 macro for the full-form must be named after the fullform_class_name specified in the corresponding CuneiTable class attribute. (See #Table with a Separate Form Modal for details.)
  2. To avoid name conflicts of the form's serach modals with other modals on the page, a full-form macro is recommended to accept and work with modal_shift argument.
  3. To avoid having to create a full-form system for every small variation in use cases, a full-form macro is recommended to accept and work with schcol_shift and fillcol_shift arguments.

Note that requirements 2 and 3 are not at all compulsory and can totally be ignored if there is no risk of modal name conflicts or usage variation. However, due to these requirements, the pattern of a typical full-form macro is as shown below.

{% macro <macro_name>(form, draw_submit=true, hide_submit=false, 
                      confirm_first=[gettext("This will delete..."), "code"],
                      modal_shift={}, schcol_shift={}, fillcol_shift={}) %}

    {% set sch_modals = {"<key0>": "<default_search_modal_name0>", ...} %}
    {% set sch_cols   = {"<key0>": "<default_search_column0>", ...} %}
    {% set fill_cols  = {"<key0>": "<default_fill_specs0>", ...} %}

    {% do sch_modals.update(modal_shift) %}
    {% do sch_cols.update(schcol_shift) %}
    {% do fill_cols.update(fillcol_shift) %}

    {{ CuneiForms.general_form_head(form) }}
        // Put form fields here.
        {% if draw_submit %}
            {% if not(hide_submit) %}
                <div class="row align-items-start">
                    <div class="col">{{ CuneiForms.render_submit(form, form.submit) }}</div>
                    {% if form.del_submit %}
                        <div class="col">
                            {{ CuneiForms.render_submit(form, form.del_submit, class="danger",
                                                        confirm_first=confirm_first) }}
                        </div>
                    {% endif %}
                    {% if form.is_del %}
                        <div class="col">{{ CuneiForms.render_check(form, form.is_del) }}</div>
                    {% endif %}
                    </div>
            {% else %}
                {{ CuneiForms.render_submit(form, form.submit, hidden=true) }}
                {% if form.del_submit %}
                    {{ CuneiForms.render_submit(form, form.del_submit, class="danger",
                                                confirm_first=confirm_first, hidden=true) }}
                {% endif %}
                {% if form.is_del %}
                    {{ CuneiForms.render_check(form, form.is_del, hidden=true) }}
                {% endif %}
            {% endif %}
        {% endif %}
        <script type="text/javascript">
            // Scripts are here.
        </script>
    {{ CuneiForms.general_form_tail(form, populate=false) }}
{% endmacro %}

In this pattern, specifications of search-and-fill fields can be a little clunkier than usual. For example, where one might normally write for a free-standing form...

{{ CuneiForms.render_blank(form, form.x_code, 
                           search="ModalAccCode:code",
                           fill="stid:x_stid,name:x_name") }}

becomes...

{{ CuneiForms.render_blank(form, form.x_code,
                           search=sch_modals["xcode"]+":"+sch_cols["xcode"],
                           fill=fill_cols["xcode"]) }}

General Table Block & Table Object

general_table_block(table, block_height,
                    block_width=null, content_width=false,
                    is_master=false, pad_bottom=true,
                    head_class="dark", head_bg=false,
                    force_head_textwhite=false, 
                    modal_shift={}, schcol_shift={}, fillcol_shift={},
                    skip_fullform=false, intab=false)
Parameters DO NOT rely on argument order farther than table, block_height, and block_width, ALWAYS PROVIDE argument keywords.
  • table (CuneiTable)
  • block_height (str): The height of the table block. The value can be anything interpretable as CSS size.
  • block_width (str): The width of the table block. The value can be anything interpretable as CSS size. If not given, CuneiFox automatically assigns the value '85vw'.
  • content_width (str): The width of the actual <table> element (scrollable inside the table block). The value can be anything interpretable as CSS size. If not given, CuneiFox automatically assigns the same value as block_width.
  • is_master (bool): Whether the table is the main element of the multi-component page.
  • pad_bottom (bool): Whether the table block should have some padding at the bottom. Usually set to javascript when the table has some immediately related components to the bottom, e.g. a mirror table or a sum fields (defined via a separate CuneiForm).
  • head_class and head_bg take the formats of class and custom_color arguments of CuneiForm's render_btn respectively. They dictates the colour of the table header as well as the color scheme of the full-form modal (when applicable). Note that, if both are given, head_bg takes precedence.
  • force_head_textwhite (bool): Forces the table header text to be coloured white. (This argument is only effective when head_class is. When using head_bg, the header text colour is controlled by head_bg[1].)
  • modal_shift, schcol_shift, and fillcol_shift (dict) are mapping dicts for search modals, search columns, and fill specs respectively. They work to map the original values coded in the full-form macro to the practical ones to really be used on the page. Cases where these arguments prove useful include pages where there are multiple search fields referring to the same databases; for example, the Accounting Journal page where the tax ID can be searched from both the VAT table and the withholding tax table. (Refer back to #Full-form Macro if needed.)
  • skip_fullform (bool): Whether the drawing of the full-form should be ignored. This is mostly done in cases where the CuneiTable itself is a few search layers in and opening more modals is more likely to cause more unpleasant experience than the convenience gained.
  • intab (str): The id of the tab holding this table. This value control the navigation to/away from the tab when the table gets or loses focus.

The Working of General Table Block

In essence, the general_table_block macro is responsible for the following actions:

  1. Create translation maps for choices and radios.
  2. Draw the full-form and its pop-up modal (if applicable).
  3. Register the table to the all_tbfm_ids list for future reference.
  4. Create a table_obj corresponding to the table, and assign preliminary values to its many attributes.
  5. Set up handler functions for the table action add, edit, delete, copy, submit, cancel, view, select, and follow link. (Each function is only set up once and shared among all the tables on the page.)
  6. Point the main component object attribute of the modal object referenced by table.in_modal (modal_obj.main_comp_obj) toward the newly created table_obj (if applicable).
  7. Draw the table's in-line form. (Only the General Form Head and the fields are handled at this stage.)
  8. Draw the table's live search field (if applicable).
  9. Draw the table's button set (if applicable).
  10. Draw blank table header and body elements.
  11. Programmatically fill in the table header.

Table Object Attributes

Below is the list of and notes on table object attributes:

  • Basic Attributes
    • id: Takes on the value of table._id.
    • self_type: Takes on value 'table'.
  • Copied Attributes (See #Notes on Keyword Arguments for details.)
    • cnames, colnames, colsizes, coltypes, editable, linkmode, modal_view, multi, perm_bit, populate_route, populate_suppress, pseudohides, searchable, seq, slim, and tbexpand: Takes on the exact value.
    • csssizes: A list of reinterpretations of table.colsizes into the format ready to plug into CSS stylings. The sizes at this stage also takes into account the table block width, the table element width, and minimum column sizes.
    • populate_id: Takes on the exact value, with all prefix # cut once event-binding is taken care of.
    • in_modal and bound_modal: Point to the corresponding modal objects where applicable.
    • intab: The id of the tab in which the <table> element belongs.
    • form: The id of the table's in-line form (table.form._id).
    • fullform: The id of the table's full-form (table.fullform[3]._id) when applicable.
    • choices: The value of table.choices with choices repacked as dict-type objects.
    • radios: The value of table.radios with choices repacked as Map objects.
  • References to DOM Elements & Objects
    • table_ele: Points to the corresponding <table> DOM element.
    • table_head_ele: Points to the corresponding <thead> DOM element.
    • table_body_ele: Points to the corresponding <tbody> DOM element.
    • progress_ele: Points to the corresponding progress bar <div> element. This might have already been a relic of the past, but it is still around in case it is needed.
    • searchbox_ele: Where applicable, points to the corresponding live-search <input> DOM element.
    • haven: Points to the corresponding haven <div> DOM element. The haven is where the row values live when they are switched out for in-line form fields, and where the form fields live when they are not active.
    • btngrp_eles: An array of button-related elements for easy references. Its members are, in order:
      1. Add <button>
      2. Copy <button>
      3. Edit <button>
      4. View <button>
      5. Expand <button>
      6. Delete <button>
      7. OK <button>
      8. Cancel <button>
      9. First Button Set <div> which is the parent of member 1 through 8
      10. Second Button Set <div> which is the parent of member 9 through 10
    • row_eles: An array of <tr> elements for easy references.
    • check_eles: An array of row-selection checkbox <input> elements for each row. This attribute is only utilized in a table with multi-selection enabled.
    • link_eles: An array of row link <a> elements. This attribute is only utilized in a table whose linkmode is set.
    • mirror_ids: An array of element ids whose x-position must be synced with this table. Use cases include the element holding the summation fields.
  • Status Attributes
    • progress_data: Human-readable loading status for the table. Localized strings for this element is stored in the variable tbloadstr.
    • progress_num: The number of currently loaded rows.
    • populating: Whether the table in undergoing a population cycle. This state attribute prevents other populating-based routines to run in conflict.
    • press_down_from_head: Whether to dispatch an ArrowDown event to table head after the table finishes populating. This creates the select-first-item behaviour.
    • addingrec: Whether the current edit operation is of add-type (add or copy).
    • last_copy_id: The id of the latest copied entry. This value is used to re-copy a new entry once the current operation is done.
    • current_edit_idx: The row index of the currently editing/adding entry.
    • triggered: Whether the table is the triggered state. (Only triggered tables can have their forms triggered as well.)
    • unlockform: Whether the table's form is triggered.
    • prevent_rowesc: Whether the Escape key on the row is prevented. This flag prevents Escape event from propagating up from the in-line form fields to the table row itself. This attribute exists because event.stopPropagation() fails to achieve the same effect in Firefox.
    • total_row: Current row count of the table.
    • max_rowidx: Max integer used in the naming of row elements. One might think that this number should be the same as total_row, but that assumption is wrong as soon as some rows disappear upon deletion. (The value is -1 for a blank table.)
    • counter_timeout: Timeout operation for hiding tb row counter. (The counter element slides into view when the mouse cursor is within the table's bound.)
    • search_timeout: Timeout operation for the search operation via the search box. (The search is conducted 700ms after the search box value is last edited. This delay is enough to give the illusion of instancy while avoiding a lot of unnecessary operations.)
    • Button Visibility Flags: Boolean flags dictating whether certain table buttons are rendered on the client-side. These attributes are interpreted from CuneiTable's own suppress_add, suppress_copy, modal_view, and perm_bit. There are 6 in total, namely addbtn_show, copybtn_show, editbtn_show, viewbtn_show, expbtn_show, delbtn_show.
  • Event Handlers
    • btngrp_handlers: The array of button click handler functions. Its members are, in order, the handlers for add, copy, edit, view, delete, ok, and cancel. Because these handlers either work with the table's selection routine or does not linked to any specific row at all, only one of each is needed.
    • check_handlers: The array of select event handler for each table row.
    • link_handlers: The array of link handler for each table row.
  • Table Data
    • data_raw: The raw data to be displayed in the table stored as an array or arrays. The values are those responded from the server and might not be ready in terms of human-readability, but they are great to work with programmatically.
    • data: The data_raw in the displayed form.
    • headrow_count: The number of row the table head has. The attribute defaults to 1, but must be changed manually should any change is needed.
  • Additional Functions & Routines
    • post_load_cmds ([function,] or false): An array of functions to run at the end of a table-populating cycle.
    • post_submit_cmds ([function,] or false): An array of functions to run at the end of a submission cycle.
  • Selection-related Attributes
    • last_select ([int, boolean]: defaults to [-1, false]): An array of 2 members: the index of last select/deselect entries and its select/deselect state. This attribute is used to create the table's batch select/deselect behaviours (via ⇧ Shift+Click).
    • selections: The array of selected row indices.
    • selected_entries: Raw data of the tables selected entries as sent from the server (in array format). Data stored here is used, for example, by search modals to mediate the data to the requesting form.
    • selection_trackers: The array of tracker forn field specifications. In its final form, each of its members is a 2-array: the first sub-member is the form field element itself, the second sub-member is the name of the tracked column (as appears in cnames attribute). (See String & Text Area Fields for details on the form's side.)

Form Expansion Modal

This section only applies to CuneiTables with an expansion modal defined (see #In-line Form with Expansion).

In short, this section includes the design of a CuneiModal with form fields inside. Note that the form head and form tail sections are handled by general_table_block and general_table_tail respectively and need not be repeated here.

General Table Tail

At this point, the table_obj and its corresponding form_obj have been initiated, all fields and relevant elements are created; this final macro is to finalize the table's attributes and link all the elements together into a coherent working system.

general_form_tail(table, load_immediately=true, skip_fullform=false)

Parameters
  • table (CuneiTable)
  • load_immediately (bool): Whether to send an immediate data request to the server.
    • For search tables, the table element is nestled within its own search modal. Regardless of this argument, the table only sends data request when the module is invoked and dumps the data when the modal closes.
    • For free-standing tables, this argument dictates whether the table sends a data request to the server on its own (once the page finishes loading). The argument should be set to true except when the mass-populating routine (see Multi-component Page) is expected to send a collective request in the table's stead.
  • skip_fullform (bool): Whether the full-form for the table is properly drawn and rendered (when applicable). This argument informs the element bindings of existing elements. It must reflect the same argument given to general_table_block earlier.

The workings of this macro can be thus summarized:

  1. Run the general_form_tail for the table's in-line form or full-form.
  2. Process the table's populate_id attribute
    • Interpret and repack each string value into a more easily usable format.
    • Where applicable, bind a change event to the referenced fields so that they trigger a form re-populating routine when changes are detected.
  3. Finalize References to DOM Elements & Objects attribute group and bind related events.
    • Bind events to show row counter element and outer glow to emphasize table's focus stage.
    • Add button icons to the table's button sets.
    • Bind keyboard shortcuts for the table's buttons.
  4. If the table is a search table, link the table's data load/unload behaviour to the corresponding search modal.
  5. Otherwise, link the table's data loading to the page's load event, unless prohibited via the argument load_immediately.

Mirror Table & Other Table Manipulations

Tables prove to be one of the simplest way to arrange elements of the interface. This section deals with table-related macros and functions that look at a table as a page formatting tool.

Mirror Table

In cases where free standing elements need to be aligned with some columns of an existing CuneiTable, some of the easiest options starts with drawing another table with the same column sizing as the original one, or mirror tables in CuneiFox's term. A mirror table drawn using this macro:

  • Has the same number of column as the referenced CuneiTable.
  • Has each column's size synced with the referenced CuneiTable.
  • Has the horizontal scroll position synced with the referenced CuneiTable.
  • Has the ID '<id_of_ref_CuneiTable>_mirror'.
  • Always has 1 blank header row to control the column sizing.

To draw a mirror table, use the Jinja2 macro draw_mirror_table.

draw_mirror_table(table, block_width, content_width=false,
                  head_class="dark", head_bg=false, force_head_textwhite=false,
                  pad_bottom=false, slim=false, padx="yes", pady="yes",
                  row=1, head_row=0, border=false)
Parameters DO NOT rely on argument order beyond table.
  • table (CuneiTable): The CuneiTable object representing the original table.
  • block_width (str): The width of the table block. The value can be anything interpretable as CSS size.
  • content_width (str): The width of the mirror <table> element (scrollable inside the table block). The value can be anything interpretable as CSS size. If not given, CuneiFox automatically assigns the same value as block_width.
  • head_class and head_bg take the formats of class and custom_color arguments of CuneiForm's render_btn respectively. They dictates the colour of the header of the mirror table. Note that, if both are given, head_bg takes precedence.
  • force_head_textwhite (bool): Forces the table header text to be coloured white. (This argument is only effective when head_class is. When using head_bg, the header text colour is controlled by head_bg[1].)
  • pad_bottom (bool): Whether to add a mb-3bottom margin to the mirror table block.
  • slim (bool): Whether to add inner padding to each cell. (This forces any form field put in the cell to assume the size of a slim form field.)
  • padx and pady (false or str): The horizontal and vertical inner padding of each cell. These 2 arguments are only active when slim is false and default to '0.1rem' if not explicitly provided.
  • row (int): The row count of the mirror table's body (<tr> with <td> children) to be created.
  • head_row (int): The row count of the mirror table's header (<tr> with <th> children) to be created.
  • border (bool): Whether to draw the mirror table as bordered. (Up to this point in time, bordered table has been useful during iteration and debugging, but has not been used on any finished page.)

Blank Table

In more general cases, the table used in page formatting does not need to resemble an existing CuneiTable in any way. In such cases, a simple table can be drawn using the Jinja2 macro blank_table.

blank_table(table_name, block_width="100%",
            row=1, col=12, padx="yes", pady="yes",
            cell_height=false, border=false)
Parameters DO NOT rely on argument order beyond table_name.
  • block_width, row, padx, pady, border: (Refer to Mirror Table.)
  • table_name (str): The ID of the new table to be created.
  • col (int): The column count of the new table.
  • cell_height (false or str): The height of each cell. This value can be any string interpretable as CSS size.

Inserting Pre-drawn Elements into a Table

Now that we have our blank table, we can move pre-drawn elements into it. We will start with a more specific use case of insert form fields (and their corresponding labels) into a blank table via the JavaScript function stuff_form_to_tb.

stuff_form_to_tb(form_name, table_name, stuff_arrays, is_mirror="auto")
Parameters
  • form_name (str): The ID of the CuneiForm which the fields belong to.
  • table_name (str): The ID of the blank table to house the form fields.
  • stuff_arrays: The table insert specification array. Each member of the array is an array itself. Each member array has 3 or more sub-members, in order:
    • field_name (str): The name of the field to place in the table.
    • target_row (int): The row of the target cell.
    • target_col (int): The column of the target cell.
    • label_target_row (int: optional): The row of the target cell (for field label).
    • label_target_col (int: optional): The column of the target cell (for field label).
  • is_mirror ('auto' or bool): Whether the target table is a mirror table. If the argument is set to 'auto', the function shall infer the correct value from the target table's ID. (This affects the way table rows are referenced in the function.)
// Example from Accounting Journal page (2 sum fields under the journal table.)
...
<div class="row justify-content-center pt-2">
    {{ CuneiTables.general_table_block(vouch_tb, block_width="84vw", ...) }}
    {{ CuneiTables.general_table_tail(vouch_tb, false) }}
    <div class="w-100"></div>
    <div class="d-none">
        // First, draw form fields in a hidden block.
        {{ CuneiForms.general_form_head(vouchsum_form) }}
        {{ CuneiForms.render_blank(vouchsum_form, vouchsum_form.drsum, headless=true) }}
        {{ CuneiForms.render_blank(vouchsum_form, vouchsum_form.crsum, headless=true) }}
        {{ CuneiForms.general_form_tail(vouchsum_form, populate=false) }}
    </div>
    {{ CuneiTables.draw_mirror_table(vouch_tb, block_width="84vw", ...) }}
    <script type="text/javascript">
        stuff_form_to_tb("{{ vouchsum_form._id }}", "{{ vouch_tb._id }}_mirror",
                          [["drsum", 0, 2], ["crsum", 0, 3]]);
    </script>
</div>
...

While stuffing form fields and their labels into a blank table might constitute the majority of use cases, there are instances where other types of elements need to be inserted into a table. For these more general cases, we can utilize the Javascript function stuff_stuff_to_tb.

stuff_stuff_to_tb(stuff_ele, tb_ele, rc_array, innerOnly=false, is_mirror="auto")
Parameters
  • stuff_ele (str or HTML element): The text or element to be inserted into a table cell.
  • tb_ele (HTML element): The <table> element.
  • rc_array ([int, int]): The row and column indices of the desired cell respectively.
  • innerOnly (bool): Determine the type of object to be placed in the table.
    • If set to true, only the innerHTML of the element is copied into the table.
    • If set to false, the whole element is moved into the table.
  • is_mirror ('auto' or bool): Whether the target table is a mirror table. If the argument is set to 'auto', the function shall infer the correct value from the target table's ID. (This affects the way table rows are referenced in the function.)

Cell Merging

To facilitate cell merging and to preserve CuneiFox style column sizing of the whole table, it is recommended that developers use the JavaScript function merge_cell to perform cell merging, especially in CuneiTable-type tables and mirror tables.

merge_cell(table_name, merge_arrays, is_mirror="auto")
Parameters
  • table_name (str): The ID of the table on which to perform merging.
  • merge_arrays: The cell merging specification array. Each member is a 4-membered array. The sub-members are, in order:
    • start_row (int): The row index of the top-left cell.
    • start_col (int): The column index of the top-left cell.
    • row_span (int): The number of row to merge (inclusive).
    • col_span (int): The number of col to merge (inclusive).
  • is_mirror ('auto' or bool): Whether the target table is a mirror table. If the argument is set to 'auto', the function shall infer the correct value from the target table's ID. (This affects the way table rows are referenced in the function.)
Notes It is highly recommended that merge_arrays be ordered right-to-left and bottom-to-top to prevent unexpected merge behaviours or uncaught errors.

Changing Cell Style

The JavaScript function change_cell_style helps developers in changing the style attribute of a table's cells quickly.

change_cell_style(table_name, prop_arrays, is_mirror="auto")
Parameters
  • table_name (str): The ID of the table on which to perform style changes.
  • prop_arrays: The style specification array. Each member is a 3-membered array. The sub-members are, in order:
    • row_index (int): The row index of the desired cell.
    • col_index (int): The column index of the desired cell.
    • style_array: The array of property-value couple. Each pair is formatted as an array of 2 strings.
  • is_mirror ('auto' or bool): Whether the target table is a mirror table. If the argument is set to 'auto', the function shall infer the correct value from the target table's ID. (This affects the way table rows are referenced in the function.)

Example

By using all the discussed functions in combination, complex placements can be achieved. Let's take the lower portion of the Product Return Records page as an example.

  1. Let's examine our need regarding the lower section of the page first:
    • The 5 rightmost fields to be synced with the rightmost column of the table.
    • Other elements can be more arbitrary, but an arrangement with 5 rows seems reasonable enough.
    Product Return Records page
    Product Return Records page
  2. Create 3 blank tables: Green mirror table, Red blank 5x1 table, and Orange blank 5x12 table
  3. Insert the 5 fields to be synced into the Red table. The rest of the elements go into the Orange table.
  4. Put the Red table in the rightmost cell of the Green table. Now we have achieved the first item in (1).
  5. Put the Orange table in the leftmost cell of the Green table. Then, merge all the cells, except the rightmost one, to get the arbitrary area that we can work with.
  6. Example of table formatting: Pre-merge
    Example of table formatting: Pre-merge
    Example of table formatting: Merged #1
    Example of table formatting: Merged #1
  7. Merge several cells of the Orange table as shown.
  8. Example of table formatting: Merged #2
    Example of table formatting: Merged #2
  9. Apply some stylings to certain cells.
  10. Example of table formatting: Cell styling
    Example of table formatting: Cell styling
  11. Turn off table border for the finished look.
  12. Example of table formatting: Finished
    Example of table formatting: Finished

The final Jinja2 template for this particular example is as follows:

...
<div class="d-none">
    <strong id="oldvat_label">Ref. Doc. Values</strong>
    <!-- Draw the ORANGE table and the RED table respectively. -->
    {{ CuneiTables.blank_table("LowerFormFrameTb", row=5, border=false,
                               padx=false, pady=false, cell_height="2.15rem") }}
    {{ CuneiTables.blank_table("LowerFormFrameTb2", row=5, border=false, col=1,
                               padx=false, pady=false, cell_height="2.15rem") }}

    <!-- Draw the lower form and its fields -->
    {{ CuneiForms.general_form_head(lower_form) }}
    <!-- Draw all lower form fields here. -->
    {{ CuneiForms.general_form_tail(lower_form, populate=false) }}

</div>
<!-- The GREEN table is a mirror table. -->
{{ CuneiTables.draw_mirror_table(body_tb, row=1, border=false, block_width="85vw",
                                 slim=true, padx="0rem", pady="0rem") }}
...
<script type="text/javascript">
    // Place fields and labels in the ORANGE table.
    stuff_form_to_tb("LowerForm", "LowerFormFrameTb",
                        [["amt_predisc", -1, -1, 0, 10], ["trddis_note", 1, 10],
                         ["cshdis_note", 2, 10], ["vatrate", 3, 11, 3, 10],
                         ["remark", 1, 0, 0, 0], ["orig_prevat", 2, 7, 1, 7],
                         ["orig_vat", 4, 7, 3, 7], ["net_total", -1, -1, 4, 10]]);
    // Place the extra label in the ORANGE table.
    stuff_stuff_to_tb(document.getElementById("oldvat_label"),
                      document.getElementById("LowerFormFrameTb"),
                      [0,7]);
    // Place fields in the RED table.
    stuff_form_to_tb("LowerForm", "LowerFormFrameTb2",
                        [["amt_predisc", 0, 0], ["trddis", 1, 0], ["cshdis", 2, 0],
                         ["vatamt", 3, 0], ["net_total", 4, 0]]);
    // Place the ORANGE table in the GREEN table.
    stuff_stuff_to_tb(document.getElementById("LowerFormFrameTb"),
                      document.getElementById("BodyTable_mirror"),
                      [0,0]);
    // Place the RED table in the GREEN table.
    stuff_stuff_to_tb(document.getElementById("LowerFormFrameTb2"),
                      document.getElementById("BodyTable_mirror"),
                      [0,6]);
    // Change cell styles of the ORANGE table.
    change_cell_style("LowerFormFrameTb",
                        [[0, 7, [["border", "1px solid var(--dark)"],
                                 ["borderStyle", "solid solid none solid"],
                                 ["paddingTop", "0.3rem"]]],
                         [1, 7, [["border", "1px solid var(--dark)"],
                                 ["borderStyle", "none solid none solid"],
                                 ["paddingTop", "0.3rem"],
                                 ["paddingLeft", "1.5rem"],
                                 ["textAlign", "left"]]],
                         [2, 7, [["border", "1px solid var(--dark)"],
                                 ["borderStyle", "none solid none solid"],
                                 ["paddingLeft", "0.7rem"],
                                 ["paddingRight", "0.7rem"]]],
                         [3, 7, [["border", "1px solid var(--dark)"],
                                 ["borderStyle", "none solid none solid"],
                                 ["paddingTop", "0.3rem"],
                                 ["paddingLeft", "1.5rem"],
                                 ["textAlign", "left"]]],
                         [4, 7, [["border", "1px solid var(--dark)"],
                                 ["borderStyle", "none solid solid solid"],
                                 ["paddingLeft", "0.7rem"],
                                 ["paddingRight", "0.7rem"],
                                 ["paddingBottom", "0.7rem"]]],
                         [0, 10,[["paddingTop", "0.3rem"],
                                 ["paddingLeft", "1rem"],
                                 ["textAlign", "left"]]],
                         [2, 10,[["paddingBottom", "0.3rem"]]],
                         [0, 0, [["paddingRight", "2rem"],
                                 ["paddingLeft", "1rem"],
                                 ["textAlign", "left"]]],
                         [1, 0, [["paddingRight", "1rem"]]],
                         [3, 10,[["paddingTop", "0.3rem"],
                                 ["paddingLeft", "1rem"],
                                 ["textAlign", "left"]]],
                         [4, 10,[["paddingTop", "0.3rem"],
                                 ["paddingLeft", "1rem"],
                                 ["textAlign", "left"]]], ])
    // Change cell styles of the RED table.
    change_cell_style("LowerFormFrameTb2",
                        [[0, 0, [["paddingTop", "0.3rem"]]],
                         [2, 0, [["paddingBottom", "0.3rem"]]],
                         [4, 0, [["paddingBottom", "0.7rem"]]], ])
    // Merge cells in the GREEN table.
    merge_cell("BodyTable_mirror", [[0, 0, 6, 1]]);
    // Merge cells in the ORANGE table.
    merge_cell("LowerFormFrameTb",
                        [[0, 10, 2, 1], [1, 10, 2, 1], [2, 10, 2, 1], [4, 10, 2, 1],
                         [0, 4, 2, 1], [0, 7, 2, 1], [1, 7, 2, 1], [2, 7, 2, 1],
                         [3, 7, 2, 1], [4, 7, 2, 1], [1, 0, 7, 4], ]);
</script>

Notable Sub-routines

Live Search

Useful Server-side Patterns

Shortcut Route for Single-table Page & Search Table

Future Development

  • Pagination: At the moment, CuneiTable works moderately well for entries up to several thousands (depending on the power of the client-unit). However, pagination will allow more entries to be processed and rendered in a swifter manner.
  • Change Order-by Column(s): In the present incarnation, the entry order of a CuneiTable is determined from the server. Allowing users to change the ordering from the client-side will definitely boost the usefulness of CuneiTables. However, details regarding entry referencing in the JavaScript code has to be considered.