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;