Scripting Forms

Overview

In Servoy, each form has an associated JavaScript (JS) file, which governs its behavior at runtime. This file defines custom methods, handles events, and interacts with the form's base API, giving developers full control over UI elements and data manipulation.

Form at Design Time

At design time, a form represents a visual layout defined in the Servoy IDE. Developers configure UI components, data bindings, and form properties. This design-time form is connected to the JS file, where functionality is scripted to respond to user actions, control form behavior, and handle data transactions. By separating design and logic, Servoy ensures maintainability and clear structure in applications.

Form as a Runtime Object

At runtime, the form becomes an object with a base API, which allows developers to control components, manage data, and trigger custom logic. This API can be extended using the form’s JS file, allowing developers to implement custom behaviors, manage user interactions, and define dynamic data logic.

Encapsulation in Form JS

The form’s JS file supports encapsulation, offering:

  • Public methods/variables: Accessible across the entire solution, allowing interactions with other forms or modules.

  • Private methods/variables: Restricted to the form itself, ensuring internal logic is hidden and modular.

  • Protected methods/variables: Available within the form and any inherited forms, ensuring reusable and secure methods in inherited structures.

Form Methods

In Servoy, a form method is a custom JavaScript function defined within a form's script that encapsulates specific functionality or business logic. Unlike form events, which are automatically triggered by user actions or form lifecycle changes (e.g., onLoad, onShow), form methods are explicitly called by the developer to perform particular tasks. They promote code reusability, organization, and maintainability by allowing you to encapsulate frequently used logic within dedicated functions. These methods can handle tasks such as data manipulation, validation, UI updates, integrations, and more. They are defined within the form's script and can be invoked from event handlers, other methods, or even from different forms if designed as global methods.

When and How to Use Form Methods

When to Use:

  • Reusability: When you have logic that needs to be executed from multiple places within the form.

  • Organization: To keep event handlers clean by delegating complex logic to separate methods.

  • Maintenance: Easier to update and manage specific functionalities without affecting unrelated code.

  • Encapsulation: To encapsulate business logic, making the codebase more modular and understandable.

How to Use:

  • Define the Method: Within the form's script, define a function with a clear and descriptive name.

  • Call the Method: Invoke the method from event handlers, other methods, or even from different forms if necessary.

  • Pass Parameters: If needed, pass parameters to the method to make it more flexible and adaptable to different scenarios.

  • Return Values: Methods can return values to be used by the caller, enabling further processing or decision-making.

Common Use Cases with Examples

Data Manipulation (CRUD Operations)

Use Case: Handling Create, Read, Update, and Delete operations on records within a form.

Example: Saving a Record with Validation

/**
 * Saves the current record after validating the form data.
 * Called when the user clicks the "Save" button.
 */
function saveRecord() {
    if (validateForm()) {
        var success = databaseManager.saveData(foundset);
        if (success) {
            plugins.dialogs.showInfoDialog('Success', 'Record saved successfully.');
        } else {
            plugins.dialogs.showErrorDialog('Error', 'Failed to save the record.');
        }
    }
}

/**
 * Validates the form data before saving.
 * @return {Boolean} True if validation passes, false otherwise.
 */
function validateForm() {
    if (!foundset.customer_name || foundset.customer_name.trim() === '') {
        plugins.dialogs.showWarningDialog('Validation Error', 'Customer name is required.');
        return false;
    }
    if (!validateEmail(foundset.email)) {
        plugins.dialogs.showWarningDialog('Validation Error', 'Please enter a valid email address.');
        return false;
    }
    return true;
}

/**
 * Validates the email format.
 * @param {String} email - The email address to validate.
 * @return {Boolean} True if valid, false otherwise.
 */
function validateEmail(email) {
    var emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailPattern.test(email);
}

Explanation:

  • saveRecord Method: This method first calls validateForm to ensure data integrity. If validation passes, it attempts to save the data using databaseManager.saveData(foundset). It then provides user feedback based on the success of the operation.

  • validateForm Method: Checks that required fields are filled and that the email address is in a valid format.

  • validateEmail Method: Uses a regular expression to verify the email format.

When to Use: When implementing a save functionality that requires data validation before persisting to the database.

Data Retrieval and Filtering

Use Case: Searching for records based on user input or applying specific filters to display subsets of data.

Example: Searching for Customers by Name

/**
 * Searches for customers whose names contain the specified keyword.
 * @param {String} keyword - The keyword to search for in customer names.
 */
function searchCustomers(keyword) {
    foundset.find();
    foundset.customer_name = '%' + keyword + '%'; // Using wildcard for partial matches
    var results = foundset.search();
    
    if (results > 0) {
        plugins.dialogs.showInfoDialog('Search Results', results + ' customers found.');
    } else {
        plugins.dialogs.showInfoDialog('Search Results', 'No customers found matching "' + keyword + '".');
    }
}

Explanation:

  • searchCustomers Method: Initiates a find mode on the foundset, sets the search criteria using wildcards for partial matching, and performs the search. It then informs the user about the number of records found.

When to Use: When implementing search functionality to allow users to find records based on specific criteria.

Form Initialization and Reset

Use Case: Preparing the form with default values when it's loaded or resetting the form to its initial state.

Example: Initializing a New Customer Form

/**
 * Initializes the form with default values when creating a new customer.
 * Called from the onShow event.
 */
function initializeNewCustomer() {
    if (foundset.newRecord) {
        foundset.date_created = new Date();
        foundset.status = 'New';
        elements.submitButton.enabled = true;
        elements.cancelButton.enabled = true;
        application.output('New customer form initialized.');
    }
}

Explanation:

  • initializeNewCustomer Method: Checks if the current record is new. If so, it sets default values for date_created and status, and ensures that certain UI elements are enabled.

When to Use: When setting up the form for creating new records, ensuring that default values are populated, and UI elements are appropriately configured.

Data Validation

Use Case: Ensuring that the data entered by the user meets specific criteria before performing operations like saving.

Example: Validating Order Quantity

/**
 * Validates the quantity field before saving an order.
 * @param {Object} oldValue - The previous value of the quantity field.
 * @param {Object} newValue - The new value entered by the user.
 * @param {JSEvent} event - The event that triggered the change.
 * @return {Boolean} True to accept the change, false to reject.
 */
function onDataChangeQuantity(oldValue, newValue, event) {
    if (newValue < 1) {
        plugins.dialogs.showWarningDialog('Validation Error', 'Quantity must be at least 1.');
        return false; // Reject the change
    }
    return true; // Accept the change
}

Explanation: onDataChangeQuantity Method: Attached to the onDataChange event of the quantity field. It ensures that the entered quantity is at least 1. If not, it shows a warning and rejects the change.

When to Use: When implementing field-specific validations to maintain data integrity.

Business Logic Execution

Use Case: Performing complex calculations, processing workflows, or handling specific business rules within the application.

Example: Calculating Total Order Amount

/**
 * Calculates the total amount for the current order by summing up line items.
 * Called whenever an order line is added, removed, or updated.
 */
function calculateTotalAmount() {
    var total = 0;
    for (var i = 1; i <= foundset.order_lines_to_orders.getSize(); i++) {
        var line = foundset.order_lines_to_orders.getRecord(i);
        total += line.quantity * line.unit_price;
    }
    foundset.total_amount = total;
    application.output('Total order amount calculated: $' + total.toFixed(2));
}

Explanation:

  • calculateTotalAmount Method: Iterates through related order line items, calculates the total by multiplying quantity and unit price for each line, and updates the total_amount field.

When to Use: When implementing calculations or processing that depend on multiple data points or related records.

Best Practices for Using Form Methods

  1. Descriptive Naming: Name methods clearly to indicate their purpose (e.g., validateForm, saveRecord).

  2. Single Responsibility: Each method should perform a single, well-defined task to enhance readability and maintainability.

  3. Parameterization: Use parameters to make methods flexible and adaptable to different scenarios.

  4. Error Handling: Incorporate robust error handling within methods to manage unexpected situations gracefully.

  5. Reusability: Design methods to be reusable across different parts of the form or even across different forms if applicable.

  6. Documentation: Comment your methods to explain their purpose, parameters, and return values for better understanding and future reference.

  7. Modularity: Keep methods modular to allow easy testing, debugging, and updating without affecting unrelated functionalities.

Form Variables

A form variable in Servoy is a local variable that belongs to a specific form and can be used to store and manage data at the form level. Form variables are typically used to manage temporary data, control form behavior, and interact with form elements, but they are not tied to any database column. They provide a convenient way to hold values specific to a form without affecting the underlying data model.

Characteristics of a Form Variable

  • Scope: A form variable is limited to the form where it is defined.

  • Lifecycle: It exists as long as the form is loaded and is reset when the form is reloaded or closed.

  • Type: Form variables can have various data types (String, Number, Date, etc.), depending on how they are defined.

When to Use a Form Variable

Form variables are useful in scenarios where you need to:

  • Store temporary data that does not need to be persisted in the database.

  • Control the state or behavior of the form (e.g., toggle UI elements or track form status).

  • Hold user input or selections before saving them to the database.

  • Implement complex logic or calculations without cluttering the database structure.

Common Use Cases with Examples

Form Variable as a Dataprovider

Form variables can be used as dataproviders in Servoy. A dataprovider in Servoy is essentially a data source for UI elements like text fields, checkboxes, and combo boxes, allowing them to display and interact with data. Typically, dataproviders are database fields, but form variables can also be assigned as dataproviders when you want to display or bind non-persistent data in the UI.

To use a form variable as a dataprovider for a UI element, follow these steps:

  1. Create a Form Variable and define its name, data type (String, Number, Boolean, etc.), and optional initial value.

  2. Assign the Variable as a Dataprovider:

    1. Select the form element (e.g., a text field or label) that you want to bind to the form variable.

    2. In the Properties section, under Dataprovider, choose the form variable as the dataprovider for that element.

  3. Update the Form Variable Programmatically:

    • You can read or modify the form variable in your form methods, and the UI element will automatically update based on the variable's value.

Example: Displaying Non-Persistent Data Form variables as dataproviders are useful when you want to display information that doesn't come from the database and doesn't need to be saved.

Use Case: You have a calculated or temporary value, like a subtotal, that you want to display on the form without saving it to the database.

// Create a form variable called 'subtotal'
var subtotal = 0;

/**
 * Calculate and update the subtotal based on item quantity and price.
 */
function calculateSubtotal() {
    subtotal = foundset.quantity * foundset.unit_price;
}

How to Use:

  • Create a form variable called subtotal of type Number.

  • Set subtotal as the dataprovider for a label or text field on the form.

  • When the calculateSubtotal() method is called and the subtotal variable is updated, the UI element will reflect the new value.

Storing Temporary UI State

Form variables are often used to control the state of UI elements, such as enabling/disabling fields or showing/hiding certain sections of a form.

Use Case: A form variable is used to control whether a form field is editable or not, based on the current user’s role or the status of the record.

Example:

// Form variable to control edit mode
var isEditMode = false;

/**
 * Toggle the form into edit mode
 */
function toggleEditMode() {
    isEditMode = !isEditMode; // Toggle the value
    elements.myTextField.enabled = isEditMode; // Enable/Disable field based on variable value
    if (isEditMode) {
        application.output('Form is now in edit mode.');
    } else {
        application.output('Form is now in view mode.');
    }
}

How to Use:

  • Create a form variable called isEditMode of type Boolean.

  • Use this variable in the toggleEditMode() method to control whether form elements like myTextField are enabled or disabled.

  • The form switches between view and edit modes when a user clicks a button.

Holding User Input Before Saving

Form variables are useful for holding user input, such as search terms or selections, before the data is processed or saved to the database.

Use Case: A form variable holds a user’s search term before running a search query.

Example:

// Form variable to store the search term
var searchTerm = '';

/**
 * Execute search based on the input in the search field
 */
function performSearch() {
    if (searchTerm && searchTerm.trim() !== '') {
        foundset.find();
        foundset.name = '%' + searchTerm + '%'; // Use the form variable to filter records
        foundset.search();
        application.output('Search results for: ' + searchTerm);
    } else {
        plugins.dialogs.showWarningDialog('Input Error', 'Please enter a search term.');
    }
}

How to Use:

  • Create a form variable called searchTerm of type String.

  • Use this variable to store the user’s input from a search field.

  • When the user clicks a Search button, the performSearch() method is triggered, using the value of searchTerm to filter records.

Toggling UI Visibility

Form variables can be used to show or hide certain elements on a form based on user interactions or conditions.

Use Case: A form variable is used to toggle the visibility of a "details" panel when a user clicks a button.

Example:

// Form variable to control the visibility of the details panel
var showDetails = false;

/**
 * Toggle the visibility of the details section
 */
function toggleDetails() {
    showDetails = !showDetails; // Toggle the value
    elements.detailsPanel.visible = showDetails; // Show/Hide the panel based on the variable
    if (showDetails) {
        application.output('Details panel is now visible.');
    } else {
        application.output('Details panel is now hidden.');
    }
}

How to Use:

  • Create a form variable called showDetails of type Boolean.

  • Use this variable to determine whether a panel or section of the form (e.g., detailsPanel) is visible or hidden.

  • Attach the toggleDetails() method to a button to toggle the visibility of the panel.

Tracking Status or Workflow

Form variables can be used to track the status of a process or workflow, such as a multi-step form where the user progresses through various stages.

Use Case: A form variable is used to track the current step in a multi-step wizard.

Example:

// Form variable to track the current step in a multi-step form
var currentStep = 1;

/**
 * Move to the next step in the form wizard
 */
function nextStep() {
    if (currentStep < 3) { // Assume the form has 3 steps
        currentStep++;
        updateFormUI(); // Update the form based on the current step
        application.output('Moved to step: ' + currentStep);
    } else {
        plugins.dialogs.showInfoDialog('Complete', 'You have completed all steps.');
    }
}

/**
 * Update the form based on the current step
 */
function updateFormUI() {
    switch (currentStep) {
        case 1:
            elements.step1Panel.visible = true;
            elements.step2Panel.visible = false;
            elements.step3Panel.visible = false;
            break;
        case 2:
            elements.step1Panel.visible = false;
            elements.step2Panel.visible = true;
            elements.step3Panel.visible = false;
            break;
        case 3:
            elements.step1Panel.visible = false;
            elements.step2Panel.visible = false;
            elements.step3Panel.visible = true;
            break;
    }
}

How to Use:

  • Create a form variable called currentStep of type Number.

  • Use this variable to track which step the user is currently on in a multi-step process.

  • The nextStep() method increments the step and updates the form's UI accordingly.

Performing Temporary Calculations

Form variables can be used to store intermediate values during calculations, such as subtotals or temporary values that are not saved to the database.

Use Case: A form variable is used to store a calculated subtotal in a sales form before the total is saved.

Example:

// Form variable to hold the calculated subtotal
var subtotal = 0;

/**
 * Calculate the subtotal based on the item quantity and price
 */
function calculateSubtotal() {
    subtotal = foundset.quantity * foundset.unit_price; // Calculate the subtotal
    elements.subtotalLabel.text = 'Subtotal: ' + subtotal; // Display the result in the form
    application.output('Subtotal calculated: ' + subtotal);
}

How to Use:

  • Create a form variable called subtotal of type Number.

  • Use the calculateSubtotal() method to calculate and display the subtotal for an order, without saving it to the database.

  • The form variable can be used to temporarily hold this value for further processing.

Form Events

A form event in Servoy is a specific type of method that is triggered automatically by the Servoy framework when certain actions or changes occur in a form. These events are tied to the form’s lifecycle (e.g., loading, showing, hiding) or interactions (e.g., user selecting records, data changes). Form events are particularly useful for handling initialization, clean-up, and reacting to changes in the form’s data or state.

Common Types of Form Events

  • onLoad: Triggered when the form is loaded for the first time (before it's shown).

  • onShow: Triggered each time the form is shown (e.g., when switching between forms or tabs).

  • onHide: Triggered when the form is about to be hidden (e.g., before navigating away from the form).

  • onRecordSelection: Triggered when a user selects a different record in the form.

For more details, see the Form Events documentation.

When to Use Form Events

You use form events when you need to perform actions based on the form’s lifecycle or user interactions. Some common scenarios include:

  • Initializing form components or setting default values when the form is loaded or shown.

  • Saving data or cleaning up resources when a form is hidden or closed.

  • Reacting to user input when they select different records or modify data.

  • Performing custom validation or calculations when specific fields are changed.

Common Form Events and Use Cases with Examples

onLoad Event

The onLoad event is triggered the first time the form is loaded into memory. It is commonly used to initialize form variables, configure UI elements, or load data into the form.

Use Case: Initialize certain form elements or variables when the form is loaded.

Example:

/**
 * This method is called when the form is loaded for the first time.
 * It's commonly used to initialize elements or set default values.
 */
function onLoad(event) {
    // Disable a text field by default
    elements.myTextField.enabled = false;
    
    // Set a default value for a form variable
    myFormVariable = 'Default Value';

    application.output('Form loaded for the first time.');
}

How to Use:

  • You can set the onLoad event in the form properties, and it will be called automatically when the form is first loaded.

  • This event is typically used to perform one-time initialization tasks.

onShow Event

The onShow event is triggered every time the form is shown. This can happen when switching between forms or tabs or when reloading the form. This event is useful for refreshing data, resetting the UI, or showing specific information to the user.

Use Case: Reload data from the database or reset UI components each time the form is displayed.

Example:

/**
 * This method is called every time the form is shown.
 * It's commonly used to refresh data or reset the form state.
 */
function onShow(firstShow, event) {
    if (firstShow) {
        // Only execute this block when the form is shown for the first time
        application.output('Form shown for the first time.');
    } else {
        // Refresh the foundset (data) every time the form is shown
        controller.loadAllRecords();
        application.output('Form reloaded.');
    }
}

How to Use:

  • Assign this event in the form properties under onShow.

  • You can use the firstShow parameter to differentiate between the first time the form is shown and subsequent displays.

  • This event is ideal for refreshing or reloading data that might change between displays.

onHide Event

The onHide event is triggered when the form is about to be hidden (e.g., when navigating to another form or closing a form). It’s useful for saving data, confirming navigation, or performing clean-up tasks.

Use Case: Prompt the user to confirm navigation if there are unsaved changes.

Example:

/**
 * This method is called when the form is about to be hidden.
 * It can be used to prompt the user or clean up resources.
 */
function onHide(event) {
    if (databaseManager.hasEditedRecords()) {
        // Ask the user if they want to save unsaved changes
        var answer = plugins.dialogs.showQuestionDialog('Unsaved Changes', 'You have unsaved changes. Do you want to save them?', 'Yes', 'No');
        if (answer === 'Yes') {
            databaseManager.saveData(); // Save changes
            return true; // Allow the form to be hidden
        } else {
            return false; // Prevent the form from hiding
        }
    }
    return true; // Allow the form to hide if there are no unsaved changes
}

How to Use:

  • Set the onHide event in the form properties.

  • This event is useful for ensuring data integrity, asking for user confirmation, or cleaning up resources before the form is hidden.

onRecordSelection Event

The onRecordSelection event is triggered every time the user selects a different record in the foundset (the set of records displayed on the form). It’s useful for updating form fields or performing actions specific to the selected record.

Use Case: Display additional information when a user selects a different record.

Example:

/**
 * This method is called every time the user selects a new record.
 * It's commonly used to update the UI based on the selected record.
 */
function onRecordSelection(event) {
    var selectedRecord = foundset.getSelectedRecord();
    if (selectedRecord) {
        application.output('Selected record: ' + selectedRecord.name);
        elements.detailsPanel.visible = true; // Show additional details
    } else {
        elements.detailsPanel.visible = false; // Hide details if no record is selected
    }
}

How to Use:

  • Assign this event to the form, and it will trigger automatically when a user selects a new record.

  • This is particularly useful when displaying master-detail forms or updating related fields based on the selected record.

Advanced Features

Form Instances

A form instance in Servoy refers to an individual, dynamically created version of a form. Normally, when you design a form in Servoy, it’s a single reusable template that can display multiple records. However, form instances allow you to create multiple independent versions of the same form at runtime, each with its own unique state, data, and UI elements. This is useful in scenarios where you need multiple copies of a form to be shown simultaneously, but with different data or configuration.

Key Characteristics of Form Instances:

  • Each form instance is treated as a separate object, allowing you to manipulate it independently.

  • Form instances can have different foundsets (sets of records), methods, and properties, even though they share the same form design.

  • This allows you to work with multiple datasets or configurations on the same form template.

When to Use Form Instances: Form instances are useful when:

  • You need to display the same form multiple times, but with different datasets or configurations (e.g., side-by-side views of data).

  • You want to work with different foundsets on the same form.

  • You need pop-up windows or dialogs that are based on the same form but with different parameters.

  • You want to maintain independent states for each version of the form, such as filters, selections, or user interactions.


In Servoy, application.createNewFormInstance is a method used to create a new instance of a form at runtime. This new form instance behaves independently of the original form and can have its own data, UI state, and behavior. This method is especially useful when you want to display the same form multiple times but with different datasets or UI configurations, such as in pop-up dialogs, side-by-side comparisons, or for managing multiple editing sessions.

Key Points About application.createNewFormInstance:

  • Creates an Independent Instance: The newly created form instance is independent of the original form. It is effectively a clone of the form, but it can operate with a different foundset or with modified UI components.

  • Maintains Structure: The new form instance keeps the structure, layout, and components of the original form. However, its behavior and data can be customized for each instance.

  • Does Not Duplicate Logic: It doesn't duplicate the underlying logic or code. It simply provides a unique instance to work with separately.

  • Naming: Each form instance must be given a unique name when it's created, so that it can be referenced individually during runtime.

Example

Use Case: You have a list view form displaying multiple records (e.g., a customer list), and you want to allow users to open multiple non-modal dialog windows showing the details of individual records. Each dialog will display details of a selected record in a detail view, and the user can open several dialog windows at the same time, independently editing different records.

In this use case, we have three forms:

  • dialog_base: An abstract form that serves as the base form, allowing other forms to inherit its behavior. It contains a generic method to open instances of itself in dialog windows.

  • orders_detail: A detailed view form that extends dialog_base and is used to show detailed information about individual orders.

  • list_view: A form that displays a list of orders in a data grid, and when a user double-clicks an order, it opens a detailed view of that order in a non-modal dialog using the openInstance method from dialog_base.

The goal is to provide a user interface where:

  • A list of order IDs is shown in a list view (using list_view form).

  • When a user double-clicks on any order in the list, the detailed information for that order opens in a non-modal dialog (using the orders_detail form).

  • Each dialog can open independently, allowing multiple order details to be viewed or edited simultaneously.

Forms Breakdown:

  1. dialog_base Form (Abstract Form) This form is the foundation for opening instances of forms as dialogs. It provides two main methods:

    • openInstance(): Creates a new instance of the form if it doesn’t exist and then opens that instance using the open() method.

    • open(): Loads the selected record into the form’s foundset and opens it in a dialog window.

    /**
     * Opens a new instance of the form for the provided record.
     * 
     * @param {JSRecord} data - The selected record.
     * @param {Number} window_type - The type of window to open (e.g., JSWindow.DIALOG).
     * @param {Boolean} decorate - Whether to decorate the window (title bar, etc.).
     * @return {Boolean} - Whether the instance was opened successfully.
     */
    function openInstance(data, window_type, decorate) {
        // Create a unique form instance name based on the primary key of the selected record
        var formName = controller.getName() + '_' + data.getPKs().join('_');
        
        // Create a new form instance if it doesn't already exist
        if (!forms[formName]) {
            application.createNewFormInstance(controller.getName(), formName);
        }
        
        /** @type {RuntimeForm<dialog_base>} */
        var form = forms[formName];
        return form.open(data, window_type, decorate); // Open the form instance
    }
    
    /**
     * Opens the form in a dialog window, loading the provided data.
     * 
     * @param {JSRecord} data - The selected record.
     * @param {Number} window_type - The type of window (modal or non-modal).
     * @param {Boolean} decorate - Whether to show a title bar and window decorations.
     * @return {Boolean} - Whether the form was opened successfully.
     */
    function open(data, window_type, decorate) {
        selected = false;
        foundset.loadRecords(data.foundset); // Load the record into the foundset
    
        // Create or get the window for the form
        var win = application.getWindow(controller.getName());
        if (!win) {
            win = application.createWindow(controller.getName(), window_type);
            win.undecorated = !decorate; // Remove title bar if decorate is false
            win.setCSSClass('dialog-style');
            
            // Randomize window position within certain bounds
            var x = Math.random() * application.getActiveWindow().getWidth();
            var y = application.getActiveWindow().getHeight() / 2.5;
            if (x > (application.getActiveWindow().getWidth() - 200)) {
                x = application.getActiveWindow().getWidth() - 200;
            }
            win.setLocation(x, y);
        }
        
        // Show the form instance in the window
        win.show(this);
        
        return selected;
    }
  2. orders_detail Form (Extends dialog_base) The orders_detail form is a detail view for displaying and editing order data. It extends dialog_base, meaning it inherits the openInstance() and open() methods to open itself in a dialog. The orders_detail form is tied to the orders table with a separate foundset (namedFoundset set to "separate"), meaning each instance of the form works independently of others.

  3. list_view Form (List View of Orders) The list_view form displays a list of orders in a data grid. When the user double-clicks on an order, it triggers the onCellDoubleClick() method, which opens a detailed view of that order in a non-modal dialog.

/**
 * This method is triggered when the user double-clicks on a row in the data grid.
 * It opens a dialog instance of the `orders_detail` form for the selected order.
 * 
 * @param {Number} foundsetindex - The index of the selected record in the foundset.
 * @param {Number} columnindex - The column index that was clicked.
 * @param {JSRecord} record - The record object for the selected order.
 * @param {JSEvent} event - The event object that triggered this action.
 */
function onCellDoubleClick(foundsetindex, columnindex, record, event) {
    // Open a new instance of the orders_detail form in a non-modal dialog
    forms.orders_detail.openInstance(foundset.getSelectedRecord(), JSWindow.DIALOG, true);
}

In this method:

  • onCellDoubleClick() retrieves the selected order record from the list.

  • The openInstance() method of the orders_detail form is called, which opens the order's detailed information in a non-modal dialog.


Full Flow Explanation:

  1. List View Display (list_view form):

    • The list_view form shows a list of order IDs using a data grid.

    • When the user double-clicks on any order, the onCellDoubleClick() method is triggered.

  2. Open Dialog (orders_detail form):

    • The onCellDoubleClick() method calls the openInstance() method from the orders_detail form (which inherits this from dialog_base).

    • The openInstance() method creates a new instance of the orders_detail form for the selected order, ensuring that each dialog window is unique.

    • The open() method in dialog_base then loads the selected order record into the form's foundset and opens a non-modal dialog showing the detailed order information.

  3. Multiple Dialogs:

    • Because each instance is uniquely identified by the order's primary key, users can open multiple dialogs for different orders at the same time.

    • Each dialog is independent, allowing users to view or edit multiple orders without interfering with each other.

Inheritance

Form inheritance in Servoy is a feature that allows one form to inherit the structure, logic, and behavior of another form, similar to how class inheritance works in object-oriented programming. The inheriting form (known as the subform) can reuse the components, properties, and methods from the parent form, while also adding its own unique elements and functionality. This is a powerful way to create modular, reusable forms and maintain consistent behavior across multiple forms without duplicating code or design.

Key Characteristics of Form Inheritance:

  • Parent Form: The base form that provides common elements (e.g., fields, buttons) and methods (e.g., validation, event handling). This form can be abstract, meaning it is never used directly but only inherited.

  • Subform: A form that inherits from the parent form. It can customize or extend the inherited behavior by adding its own elements, overriding methods, or adding new methods.

  • Inheritance of UI Components: The subform automatically inherits the UI components (such as buttons, fields, and labels) of the parent form but can add or modify them as needed.

  • Reusability: By using inheritance, you can reduce duplication of UI components and logic, making forms easier to maintain and extend.

Why Use Form Inheritance?

  • Code Reusability: Common functionality (such as validation, navigation, or event handling) can be placed in a parent form and reused across multiple subforms.

  • Consistency: Ensures consistent look and behavior across multiple forms, especially useful for creating standardized layouts or workflows.

  • Simplified Maintenance: If a change is needed in the common functionality, it can be done in the parent form and will automatically apply to all subforms.

  • Modularity: You can define general-purpose, reusable components in the parent form and specialize them in subforms for different use cases.

For more information on inheritance, see the Form Inheritance Guide.

Custom Design-Time Properties

Custom Design-Time Properties in Servoy are developer-defined properties that allow you to assign additional metadata to a form during development. These properties are available in Servoy Developer but are not directly visible to the end-user or application at runtime. They can be used to store configuration settings, preferences, or other contextual information related to how the form should behave or appear.

Key Points About Custom Design-Time Properties:

  • Customizable: You can create key-value pairs for any type of configuration or metadata you need for the form.

  • Design-Time Only: These properties are only accessible during design or development and are not stored or visible at runtime unless explicitly accessed through scripting.

  • Flexible: You can use them to store different settings, including layout preferences, behavior modes (read-only, editable), themes, or any other relevant metadata that you want to access dynamically.

How and When to Use Custom Design-Time Properties When to Use:

  • Modular Configuration: To make forms behave differently based on settings that can be controlled by developers during design-time without hard-coding.

  • Theming/Styling: To apply different visual styles or themes to a form.

  • Behavior Control: To define how forms behave (e.g., read-only or editable) without changing the logic for each form manually.

  • Localization: To set language or locale-specific data or translations for a form.

How to Set Custom Design-Time Properties: In Servoy Developer, you can define custom design-time properties directly in the form editor:

  1. Open the form in Servoy Developer.

  2. In the Properties Panel, there is a section for Design-Time Properties.

  3. You can manually add key-value pairs to store your custom properties. For example, you might add:

    • "theme": "dark"

    • "mode": "read-only"

    • "version": "1.0"

How to Access Custom Design-Time Properties in JavaScript: You can retrieve and use custom design-time properties in the form’s JavaScript file using the solutionModel.getForm() method and the getDesignTimeProperty() function.

Syntax:

var form = solutionModel.getForm(controller.getName());
var value = form.getDesignTimeProperty('propertyName');

Example Use Cases for Form's Custom Design-Time Properties:

Theming (Light/Dark Mode)

You can define a custom property like "theme": "dark" or "theme": "light" to control the appearance of the form based on the developer's choice. At runtime, you can dynamically change the form’s UI (like colors or styles) based on this property.

Define Property:

  • In the form's properties panel, add "theme": "dark" for a dark theme.

Access and Use in JavaScript:

function applyTheme() {
    // Get the form's design-time properties
    var form = solutionModel.getForm(controller.getName());
    var theme = form.getDesignTimeProperty('theme');
    
    // Apply theme based on the design-time property
    if (theme === 'dark') {
        // Apply dark theme styles
        elements.myPanel.bgcolor = '#333333';
        elements.myLabel.fgcolor = '#FFFFFF';
    } else if (theme === 'light') {
        // Apply light theme styles
        elements.myPanel.bgcolor = '#FFFFFF';
        elements.myLabel.fgcolor = '#000000';
    }
}

In this example:

  • The design-time property "theme": "dark" or "theme": "light" determines how the form should be styled (e.g., background and foreground colors).

  • This allows you to have different versions of the form without modifying the code for each one.

Form Mode (Read-Only or Editable)

You might want to define whether a form is read-only or editable at design time. This can be useful if you have the same form used in different parts of the application where some should allow editing and others should be read-only.

Define Property:

  • Add "mode": "read-only" or "mode": "editable" in the form’s custom design-time properties.

Access and Use in JavaScript:

function setFormMode() {
    // Access the form and its design-time properties
    var form = solutionModel.getForm(controller.getName());
    var mode = form.getDesignTimeProperty('mode');
    
    // Set form components to read-only or editable based on the design-time property
    if (mode === 'read-only') {
        elements.myTextField.enabled = false;
        elements.myComboBox.enabled = false;
    } else if (mode === 'editable') {
        elements.myTextField.enabled = true;
        elements.myComboBox.enabled = true;
    }
}

In this example:

  • The form’s mode (read-only or editable) is controlled by the custom design-time property "mode": "read-only" or "mode": "editable".

  • You can change the behavior of the form’s components (e.g., disabling or enabling input fields) based on the property.

Conditional Behavior or Features

Sometimes you might want to enable or disable certain features in a form based on design-time properties. For instance, you can define whether a feature is enabled by default.

Define Property: Add "featureXEnabled": true in the custom design-time properties.

Access and Use in JavaScript:

function checkFeatureAvailability() {
    // Get the form's design-time properties
    var form = solutionModel.getForm(controller.getName());
    var isFeatureEnabled = form.getDesignTimeProperty('featureXEnabled');
    
    // Enable or disable the feature based on the property
    if (isFeatureEnabled) {
        elements.featureXPanel.visible = true;
    } else {
        elements.featureXPanel.visible = false;
    }
}

In this example:

  • The design-time property controls whether a certain feature (represented by featureXPanel) is visible or available on the form.

Last updated