diff --git a/docs/4DQodlyPro/gettingStarted.md b/docs/4DQodlyPro/gettingStarted.md
index 76aff60b3..40f40045f 100644
--- a/docs/4DQodlyPro/gettingStarted.md
+++ b/docs/4DQodlyPro/gettingStarted.md
@@ -34,9 +34,10 @@ The recommended resolution is 1920x1080.
#### Project
-Qodly Studio only works with 4D projects (binary databases are not supported).
+Qodly Studio only works with 4D [projects](https://developer.4d.com/docs/Project/overview) (binary databases are not supported).
- Web sessions (*aka* Scalable sessions) must [be enabled](https://developer.4d.com/docs/WebServer/sessions#enabling-web-sessions).
+- The ["forceLogin" mode](https://developer.4d.com/docs/REST/authUsers#force-login-mode) must be [activated](https://developer.4d.com/docs/settings/web#activate-rest-authentication-through-dsauthentify-function) to handle web sessions.
- The 4D code called by Qodly forms must be [thread-safe](https://developer.4d.com/docs/WebServer/preemptiveWeb).
@@ -60,7 +61,8 @@ All the [configuration requirements](#requirements) can be automatically set for
:::note
- Only settings that need to be edited are listed in the dialog box.
-- Since scalable sessions run in preemptive mode, enabling this setting might require that you evaluate the thread-safety property of your code.
+- Since scalable sessions run in preemptive mode, enabling this setting might require that you evaluate the [thread-safety property](https://developer.4d.com/docs/Develop/preemptive-processes#writing-a-thread-safe-method) of your code.
+- Activating the "forceLogin" mode might require that you reconfigure the REST accesses, [as explained in this blog post](https://blog.4d.com/force-login-becomes-default-for-all-rest-auth/).
:::
@@ -170,7 +172,7 @@ Qodly Studio encapsulates Qodly pages, including layout, data connections, and e
:::info
-See [this page](../4DQodlyPro/rendering.md) for detailed information on how to render Qodly pages in Qodly.
+See [this section](../4DQodlyPro/rendering.md) for detailed information on how to render Qodly pages in Qodly.
:::
diff --git a/docs/4DQodlyPro/img/enable-settings.png b/docs/4DQodlyPro/img/enable-settings.png
index 41ad56e9f..e5aea5bee 100644
Binary files a/docs/4DQodlyPro/img/enable-settings.png and b/docs/4DQodlyPro/img/enable-settings.png differ
diff --git a/docs/4DQodlyPro/notes.md b/docs/4DQodlyPro/notes.md
new file mode 100644
index 000000000..07e398f54
--- /dev/null
+++ b/docs/4DQodlyPro/notes.md
@@ -0,0 +1,56 @@
+---
+id: release-notes
+title: Release Notes
+---
+
+
+
+## 4D 21
+
+### Highlights
+
+- [Localization (i18n)](./localization.md): Launched built-in Localization support, allowing you to create multilingual applications visually—without coding. You can define supported locales, manage translation keys and literals, preview translations directly in the Studio, and allow users to switch languages at runtime using the [UserLanguage](pageLoaders/qodlySources.md#qodlysource-userlanguage) shared source.
+
+- [Page Zoom Controls](pageLoaders/pageLoaderOverview.md#page-zoom-controls): in the header panel, allowing users to adjust the page’s zoom level for more precise component placement and layout editing.
+
+- [Events Report](pageLoaders/pageLoaderOverview.md#events-report): Introduced the Events Report, a visual overview of all page events for components and Qodly sources complete with filtering, editing, and navigation options.
+
+... more to come...
+
+
+
+## 4D 20 R10
+
+### Highlights
+
+- [Qodly Looker Studio Connector](../Integrations/qodlyLookerStudio/qodlyLookerStudioConnector.md): Added integration between Qodly applications and Google Looker Studio, enabling users to create interactive dashboards, track real-time business metrics, and generate custom reports using Qodly data.
+
+- [Saved Condition Go To Button](pageLoaders/states/conditionalState.md#saved-condition-integration): When a saved condition is integrated into a state, a **Go to** button now appears next to its name. Clicking it opens the full saved condition in edit mode—so you can quickly review or update it without leaving the schema view.
+
+- [Interval Range Validation for Text Input](pageLoaders/components/textinput.md#intervals-for-date-input): For text inputs using the **interval type `Range`**, if the **start date is later than the end date**, an error message will be shown and the dates will be temporarily disabled until corrected.
+
+- [Matrix Selection Behavior Options](pageLoaders/components/matrix.md#properties-customization): You can now control how the **Matrix** behaves after a data update (like reloading or filtering).
+
+- [Debugger Sidebar](./debugging.md#debugger-sidebar): A new sidebar in the code editor lets you monitor, group, enable/disable, delete, and jump to breakpoints across your entire app. It also shows a [Variables panel](./debugging.md#variables-panel) during debug sessions, so you can view local variables, current line variables, and method arguments—all in one place.
+
+- [Built-in Shared Qodly Namespace](pageLoaders/qodlySources.md#built-in-shared-qodly-namespace): Introduced a built-in Qodly namespace available across all application pages. It provides ready-to-use qodlysources for shared data handling, including a [Location shared qodlysource](pageLoaders/qodlySources.md#qodlysource-location) that simplifies working with URL segments, query parameters, and anchors. As well as, a [UserLanguage shared qodlysource](pageLoaders/qodlySources.md#qodlysource-userlanguage) that allows runtime management of user-selected languages and lists supported locales dynamically; and a [Title shared qodlysource](pageLoaders/qodlySources.md#qodlysource-title) that enables setting the browser tab title dynamically at runtime.
+
+- [Connection Status Handling in Renderer](rendering.md#connection-status-handling): The Renderer now displays connection status messages when the network is lost or restored during rendering. A red banner appears when disconnected, and a green banner confirms when the connection is restored.
+
+### Behavior Changes
+
+- **Renamed properties in the [intervals datasource](pageLoaders/components/textinput.md#params-object-properties)** of the text input component for consistency:
+
+ - `toDay` is now `today`
+ - `startingfrom` is now `startingFrom`
+ - `untilto` is now `until`
+
+- **Include Option and Interval Card Toggle**: Added visual controls to improve interval management. The [Include checkbox](pageLoaders/components/textinput.md#include-checkbox-within-the-card) lets users include or exclude specific date ranges, while the [card toggle](pageLoaders/components/textinput.md#card-toggle-top-right) allows enabling or disabling intervals without losing their configuration.
+
+- [HTTP Handlers UI Redesign](./httpHandlers.md): Updated the UI to provide a clearer and more intuitive layout, making it easier to configure and manage request handlers.
+
+- [Roles and Privileges UI – Button Label Update](./roles/permissionsOverview.md#clean-non-existing-resources): In the Roles and Privileges interface, the **Clear** button was renamed to **Clean** for clarity. This button appears when a resource (like an attribute) is no longer available, and lets users remove outdated permissions.
+
+- [Initial Value Editing in Qodly Source](pageLoaders/qodlySources.md#editing-a-qodly-source): For qodlysources with editable properties, initial values now include a **Maximize** button for fields of type `object` or `array`. Clicking the maximize icon opens a popup editor, which can also be expanded to fill the contextual panel, making it easier to edit complex or long values.
+
+- [Date Picker Navigation in Text Input](pageLoaders/components/textinput.md#embedded-input): Users can now navigate to the next/previous month and next/previous year directly within the date picker for a smoother selection experience.
diff --git a/docs/4DQodlyPro/pageLoaders/img/edit-events-report.png b/docs/4DQodlyPro/pageLoaders/img/edit-events-report.png
new file mode 100644
index 000000000..bf6bb7584
Binary files /dev/null and b/docs/4DQodlyPro/pageLoaders/img/edit-events-report.png differ
diff --git a/docs/4DQodlyPro/pageLoaders/img/events-report-switch.png b/docs/4DQodlyPro/pageLoaders/img/events-report-switch.png
new file mode 100644
index 000000000..2a72ada82
Binary files /dev/null and b/docs/4DQodlyPro/pageLoaders/img/events-report-switch.png differ
diff --git a/docs/4DQodlyPro/pageLoaders/img/events-report.png b/docs/4DQodlyPro/pageLoaders/img/events-report.png
new file mode 100644
index 000000000..6b050f310
Binary files /dev/null and b/docs/4DQodlyPro/pageLoaders/img/events-report.png differ
diff --git a/docs/4DQodlyPro/pageLoaders/img/filter-events-report.png b/docs/4DQodlyPro/pageLoaders/img/filter-events-report.png
new file mode 100644
index 000000000..fe7a0cb6c
Binary files /dev/null and b/docs/4DQodlyPro/pageLoaders/img/filter-events-report.png differ
diff --git a/docs/4DQodlyPro/pageLoaders/img/switch-event-report.png b/docs/4DQodlyPro/pageLoaders/img/switch-event-report.png
new file mode 100644
index 000000000..10c73e7f9
Binary files /dev/null and b/docs/4DQodlyPro/pageLoaders/img/switch-event-report.png differ
diff --git a/docs/4DQodlyPro/pageLoaders/img/zoom-controls.png b/docs/4DQodlyPro/pageLoaders/img/zoom-controls.png
new file mode 100644
index 000000000..78833e624
Binary files /dev/null and b/docs/4DQodlyPro/pageLoaders/img/zoom-controls.png differ
diff --git a/docs/4DQodlyPro/pageLoaders/pageLoaderOverview.md b/docs/4DQodlyPro/pageLoaders/pageLoaderOverview.md
index 9698e8f4d..1cc402b02 100644
--- a/docs/4DQodlyPro/pageLoaders/pageLoaderOverview.md
+++ b/docs/4DQodlyPro/pageLoaders/pageLoaderOverview.md
@@ -156,6 +156,36 @@ The Sanity Check serves as an informative tool. Even if errors are present, a Pa
The Qodly Sources Color button in the header panel allows easy identification of the background color of qodlysources linked to a component.
+### Page Zoom Controls
+
+The Zoom controls in the header panel lets users adjust the page’s zoom level. This helps when positioning components precisely within complex or tightly spaced layouts. By increasing or decreasing the zoom, users can focus on fine details or get a broader overview of the page structure, making design and alignment tasks more efficient.
+
+### Events Report
+
+The Events Report button displays all the event interactions defined within a page. It provides a visual overview of how components define their events, helping developers understand and debug the application’s behavior.
+
+Each node in the report represents a component or a Qodly Source event definition. Developers can explore complex event logic at a glance, without navigating through multiple panels.
+
+
+
+Users can also edit events directly within the report. Clicking the Edit an event block opens its configuration, allowing developers to modify the event’s logic, connected Qodly sources, or target components without leaving the Events Report view. This streamlines the process of fine-tuning event behavior while maintaining full visibility of related connections.
+
+The Events Report includes a Filter panel that allows users to refine the view and focus on specific types of connections:
+
+
+
+Components: Displays events triggered directly by components (e.g., Button, Stylebox...).
+
+Local QodlySources: Shows events linked to Qodly sources defined locally within the current page.
+
+Shared QodlySources: Displays events associated with shared Qodly sources across pages.
+
+Users can switch back to the Page Editor at any time using the toolbar button in the top-right corner.
+
+
+
+The Events Report is especially useful for reviewing the logic of complex pages, ensuring that event flows behave as intended, and quickly diagnosing unexpected actions or missing links.
+
### Preview
diff --git a/docs/4DQodlyPro/pageLoaders/qodlySources.md b/docs/4DQodlyPro/pageLoaders/qodlySources.md
index 35926cd22..41be0aa2d 100644
--- a/docs/4DQodlyPro/pageLoaders/qodlySources.md
+++ b/docs/4DQodlyPro/pageLoaders/qodlySources.md
@@ -93,16 +93,16 @@ The Location qodlysource is specifically designed to facilitate handling URL-rel
**Example:**
```javascript
- // URL: example.com/products/item
- Location.urlQuery // Output: ["products", "item"]
+ // URL: example.com/search?category=shoes&color=blue
+ Location.urlQuery // Output: { category: "shoes", color: "blue" }
```
- **urlPath** *(Object)*: Contains key-value pairs representing parameters that appear after the question mark (`?`) in URLs.
**Example:**
```javascript
- // URL: example.com/search?category=shoes&color=blue
- Location.urlPath // Output: { category: "shoes", color: "blue" }
+ // URL: example.com/products/item
+ Location.urlPath // Output: ["products", "item"]
```
- **anchor** *(String)*: Stores the part of the URL following the hash symbol (`#`). This is typically used for navigation within the same page or handling client-side routing.
diff --git a/docusaurus.config.js b/docusaurus.config.js
index b1c272c14..23fbdb96b 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -5,14 +5,16 @@ const lightCodeTheme = require('prism-react-renderer/themes/github');
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
const ghUrl = `${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${process.env.GITHUB_REPOSITORY || 'qodly/docs'}`;
-const isProduction = process.env.GITHUB_REPOSITORY_OWNER === 'qodly';
+//const isProduction = process.env.GITHUB_REPOSITORY_OWNER === 'qodly';
+const isProduction = process.env.GITHUB_REPOSITORY_OWNER === '4d';
/** @type {import('@docusaurus/types').Config} */
const config = {
title: '4D Qodly Pro Documentation',
tagline: 'Easily build powerful, low-code web interfaces to elevate your 4D applications.',
url: isProduction ? "https://developer.qodly.com" : "https://docqodly.github.io",
- baseUrl: "/docQodlyPro/",
+ baseUrl: isProduction ? "/qodly/" : "/docQodlyPro/",
+ // baseUrl: "/qodly/",
onBrokenLinks: 'warn',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.svg',
@@ -135,7 +137,7 @@ Thank you for helping us improve! 🚀
// },
metadata: [
{name: 'keywords', content: 'qodly documentation, qodly doc, documentation qodly, doc qodly, qodly Developer, qodly guide, qodly tutorial, qodly low-code development'},
- {name: 'description', content: 'Official documentation for Qodly developers. Learn how to use Qodly Studio, Qodlyscript, and more with detailed guides and tutorials.'},
+ {name: 'description', content: 'Official documentation for Qodly. Learn how to use Qodly Studio, desing Qodly pages and more with detailed guides and tutorials.'},
],
navbar: {
title: 'Docs', //Docs
@@ -188,6 +190,7 @@ Thank you for helping us improve! 🚀
versions:
{
current: {label: 'next'},
+ '21': {label: '21 BETA'},
'R10': {label: '20 R10'},
},
dropdownItemsAfter: [
diff --git a/sidebars.js b/sidebars.js
index 900690232..f9a73b4f1 100644
--- a/sidebars.js
+++ b/sidebars.js
@@ -19,6 +19,10 @@ const sidebars = {
type: 'doc',
id: '4DQodlyPro/gettingStarted'
},
+ {
+ type: 'doc',
+ id: '4DQodlyPro/release-notes'
+ },
],
// Qodly 4D Pro
Develop: [
diff --git a/versioned_docs/version-21/4DQodlyPro/coding.md b/versioned_docs/version-21/4DQodlyPro/coding.md
new file mode 100644
index 000000000..c35b6c9f1
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/coding.md
@@ -0,0 +1,258 @@
+---
+id: coding
+title: Coding
+---
+import Column from '@site/src/components/Column'
+
+Qodly is a groundbreaking hybrid **low-code** application development platform that redefines how you build applications. With Qodly, you'll find yourself needing only a minimal amount of code, and sometimes, no code at all. It's as simple as designing your application, and Qodly Studio takes care of generating all the necessary code on your behalf.
+
+
+While Qodly empowers you with its low-code capabilities, there are situations where coding expertise becomes essential. Within Qodly Studio, you'll harness the power of [events](pageLoaders/events/bindingActionToEvents.md) in combination with class functions to effectively manage the intricacies of your web application.
+
+
+
+## Handling methods and classes from Qodly Studio
+
+You can create, duplicate, rename, and delete 4D methods and classes directly from Qodly Studio.
+
+### Creating
+
+You can create a method or class using one of the three methods:
+
+
+
+
+
You can create them individually from dedicated grids on the Studio Homepage.
+
In the Explorer, simply click the plus icon located next to either Methods or Classes.
+
While in the Page Editor, go to the New + tab and opt for either Methods or Classes.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A new entry, labeled as UntitledN appears in the list, where N is a number incrementing with each new creation. You can provide a compliant name and press Enter to confirm the modification.
+
+
+
+
+
+
+### Duplicating
+
+
+
+ When duplicating existing methods or classes, Qodly suggests a naming convention in the itemName_copy_N format.
+
+
+
+
+
+
+### Renaming
+
+To rename a method or class, you can either:
+
+
+
+
+
Click on the icon at the right side of the item in the Explorer.
+
+
+
+
+
+
+### Deleting
+
+Deleting a method or class is straightforward:
+
+
+
+
+
In the Explorer, open the options menu for the item you wish to delete.
+
+
Select Delete.
+
+
+
+
+
+
+
+
+
+
+
Confirm the deletion in the subsequent warning dialog.
+
+
+
+
+
+
+
+### Tab Management
+
+
+
+
+ To work on a method or class, you can open it in a tab by double-clicking its name in the Explorer. Only one instance of a method or class code can be open in the same code editor window.
+ To close a tab, either click the x button or use the tab's pop-up menu
+
+
+
+
+
+
+### Executing Methods
+
+For testing purposes, a method can be executed from the Explorer
+Or directly from the toolbar
+
+:::info
+For comprehensive information on debugging your code, please refer to the [Debugger](./debugging.md) section.
+:::
+
+## Code Preservation and Synchronization
+
+Ensure the integrity and consistency of your method and class code in Qodly with these preservation and synchronization tools.
+
+### Saving
+
+Your code modifications are automatically saved at regular intervals. However, if you want to ensure immediate saving, you can click `Save all` to save all edited tabs.
+
+### Reloading code
+
+
+
+ If you're working on a class function or method, and it gets edited elsewhere, the tab displays (outdated).
+ To fetch the latest version, right-click the tab and choose Reload. This action initiates an immediate refresh of the code content directly from the server, ensuring that you are always working with the latest code, even if you haven't made any local edits.
+
+
+
+
+
+
+### Collaborative Editing Behavior
+
+Methods and class functions in Qodly Studio feature real-time synchronization when multiple users are editing the same class function. These collaborative editing capabilities are accompanied by safeguards to prevent data loss in specific scenarios. Here's how it works:
+
+
+- **Automatic Synchronization**: Whenever a user edits and saves a method or class function, those changes are instantly synchronized across all open tabs where other users are working on the same class function. This guarantees that every user has access to the most current version of the code.
+
+- **Unsaved Changes and "Outdated" Status**: Consider the scenario where `User A` makes changes to a method or class function but neglects to save them, and then `User B`, who is also working on the same method or class function, makes different changes and saves them. In this scenario, `User A`'s method or class function tab, which contains unsaved changes, will recognize that it now holds outdated content compared to the version saved by `User B`. This recognition is indicated by an `outdated` status.
+
+- **Page Refresh and Data Persistence**: Qodly Studio employs client-side data persistence through the browser's local storage to store and retrieve the state information. When `User A` initiates a page refresh, the following actions are performed:
+
+ - **Checking Local Storage**: Qodly Studio checks the local storage for any saved state data associated with the tab that `User A` was working on.
+
+ - **Retrieving Saved Data**: Upon discovery, Qodly Studio retrieves this stored data, which includes the current content of the method or class function that `User A` was editing.
+
+ - **Assessing the "Outdated" Status**: Additionally, the application assesses the `outdated` status based on an attribute within the tab state flags section. This attribute serves as an indicator of whether the current state is outdated compared to the version stored on the server.
+
+ - These measures ensure that even after a page refresh, `User A` remains fully informed about any changes made by others, such as `User B`, while preserving their own unsaved changes.
+
+
+
+
+
Reload Option: Within the method/class tab of Qodly Studio, you'll find the Reload feature. Upon selecting this option, a confirmation message will promptly appear, indicating that your local changes will be lost. Should User A choose to reload the content will trigger an immediate refresh of the code content directly from the server.
+
+
+
+
+
+
+
+
+
+
+
Save all: Should User A choose to save the outdated code in their tab, a confirmation message will appear, alerting them to the presence of new content saved by User B. This serves as a safeguard to prevent unintentional overwriting of more recent changes made by different users.
+
+
+
+
+
+
+
+- **Data Loss Prevention**: If `User A` proceeds to save the outdated code, the changes made in their tab become the current version, potentially overwriting `User B`'s changes.
+
+
+This collaborative editing behavior aligns with common practices in collaborative environments, offering users flexibility while minimizing the risk of data loss or conflicts during concurrent edits.
+
+## Code Editor
+
+### LSP
+
+Qodly Studio relies on the Language Server Protocol ([LSP technology](https://en.wikipedia.org/wiki/Language_Server_Protocol)) to offer advanced coding features like code completion and syntax highlighting.
+
+:::info
+Reloading is recommended in cases where a connection is not established. Without this connection, while you can still write and save your code, you will miss out on LSP-related features.
+:::
+
+
+### Type-ahead features
+
+Qodly Studio code editor includes helpful type-ahead and auto-completion features. You can easily incorporate these suggestions into your code using the following methods:
+
+- The suggestion list automatically appears as you start typing.
+- To insert the selected suggestion, simply press the `Tab` key.
+- At any point, you can manually trigger the suggestion list by pressing `Ctrl + Space bar`.
+
+
+### Show documentation
+
+When a QodlyScript command or function is entered in the code editor, you can get documentation on this command or function by hovering the cursor over the command or function name:
+
+- a tooltip appears, displaying the parameters of the command or function along with a short description;
+- you can also click on the **Show documentation** link within the tip, which opens the complete documentation for the command or function on [developer.qodly.com](https://developer.qodly.com/docs) in your browser.
+
+
+
+
+### Command Palette
+
+The Command Palette offers easy access to all available commands in the code editor, including any associated shortcuts. You can open it by right-clicking in the code editor window or pressing `F1`.
+
+
+
+The palette includes a filtering option to find specific commands quickly.
+
+
+
+### Warnings and errors
+
+Qodly Studio includes a Code Live Checker feature. The syntax, consistency, and structure of the entered code are automatically checked in order to prevent execution errors. For example, the Code Live Checker can detect that a right parenthesis is missing or that you used an unknown dataclass attribute.
+
+Qodly automatically checks the code syntax to see if it is correct. If you enter text or select a component that is not correct, Qodly underlines the incorrect expression. Two underline colors are used:
+
+- Yellow underlines are **warnings**
+
+Warnings are intended to draw your attention to statements that might lead to execution errors. They are not considered as coding errors.
+- Red underlines are **errors**
+
+Errors are anomalies that prevent the code from being executed correctly. It includes syntax errors, declaration errors, etc. They must be fixed, otherwise the code will not run correctly.
+
+Whatever the incorrect expression, you can move the mouse over the line to display a help tip providing the cause of the warning/error:
+
+
+
+The Code Live Checker is activated while you enter the code. When a line of a method, class or function is marked as having improper syntax, check and fix the entry. If the line becomes correct, Qodly removes the underline.
+
+The Code Live Checker checks for:
+
+- basic syntax errors (wrong operators, misspellings and the like)
+- the structure of statements (`if`, `end if` and so on)
+- matching enclosing characters in the code such as parentheses or brackets
+- the calls of attributes and functions according to your model (ORDA) and user class definitions. For example, the Code Live Checker generates an error when a call to a dataclass computed attribute is not compatible with the declared computed attribute type.
+
+The Code Live Checker cannot detect some errors that only occur during execution. Execution errors are caught by Qodly when the code is executed.
diff --git a/versioned_docs/version-21/4DQodlyPro/debugging.md b/versioned_docs/version-21/4DQodlyPro/debugging.md
new file mode 100644
index 000000000..4fcbbbec6
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/debugging.md
@@ -0,0 +1,223 @@
+---
+id: debugging
+title: Debugging
+---
+import Column from '@site/src/components/Column'
+
+Errors are a common occurrence when writing code. It is unusual to write a substantial amount of code without encountering any errors. Fortunately, dealing with and resolving errors is a routine part of the development process.
+
+
+In the Qodly development environment, you have access to a range of debugging tools to help you tackle different types of errors effectively.
+
+
+## Starting a debug session
+
+To execute your code line-by-line and evaluate expressions, you must initiate a **debug session** on the server and **attach** it to your browser. Follow these steps:
+
+
+1. Click the Debug button located in Qodly Studio toolbar.
+
+2. If the debug session starts successfully, a green bullet will appear on the button label.
+
+3. Once you begin executing code within the debug session, the bullet will turn orange.
+
+
+:::info
+
+
+ You can only have one active debug session per instance. If another instance of the application has an active debug session (e.g., started from another browser), the debug button will display a warning message.
+
+
+
+
+
+:::
+
+In such cases, you must wait until the other debug session is closed before starting a new one.
+
+:::tip
+Verify that the method or function with the breakpoint is saved and not in [draft state](#breakpoint-status) for the breakpoint to take effect during debugging.
+:::
+
+## Stopping a debug session
+
+If you wish to stop a debug session, follow these steps:
+
+1. Click the **Debug** button in Qodly Studio toolbar while a debug session is active.
+
+2. A warning dialog box will prompt you to confirm whether you want to detach the debugger, effectively closing the debug session attached to your browser. You will have several options:
+
+
+
+
+
+
Keep in progress: Qodly will continue evaluating the code until the end of the current method or function, and then the debugger will be detached.
+
Stop: The debugger will be immediately detached.
+
Cancel: The debugger will not be detached.
+
+
+
+
+
+
+
+
+
+
+## Breakpoints
+
+Breakpoints allow you to pause code execution at specific points in your code. You can set breakpoints on any line of code where you want the execution to halt. Here's how to create a breakpoint:
+
+1. Click in the left margin of the code editor. This action will display a red bullet.
+
+2. When you launch the code, a yellow arrow will appear in the left margin to indicate where the execution paused and mark the next line to be executed.
+
+3. At this point, you can use the debugger panel at the bottom of the window to evaluate and debug your code.
+
+
+:::warning
+Should a function appear unexpectedly, even without any breakpoints in your code while the debugger is active, it implies the existence of an error within that specific code section.
+:::
+
+### Breakpoint Status
+
+Breakpoints can have different statuses depending on the context, which are represented by their appearance and tip:
+
+
+|Appearance|Status|Context|
+|---|---|---|
+| |Breakpoint|The breakpoint is validated in the debug session and will pause code execution as expected.|
+||Draft breakpoint|The method or function where the breakpoint is set has not been correctly saved. Please ensure you save your changes first for the breakpoint to be validated and take effect.|
+||Unverified breakpoint|The breakpoint is saved, but no debug session is currently active. It will not pause code execution until a debug session is started.|
+||Disabled breakpoint|The breakpoint has been manually disabled. It remains visible but will not interrupt code execution until re-enabled.|
+
+## Debugger Sidebar
+
+The **Debugger Sidebar** provides a centralized interface to monitor and manage all breakpoints across an application. It is designed to give developers full visibility into their active, disabled, or grouped breakpoints—regardless of which file or method they belong to.
+
+You can open the Debugger Sidebar from the left-hand panel in the code editor:
+
+
+
+
+
Open Qodly Studio.
+
In the code editor, look for the vertical tab on the left side.
+
Click the sidebar icon to open the Debugger Sidebar
+
+
+
+
+
+
+
+:::info
+You can interact with all breakpoints even if they’re in different files — no need to open each one manually. The Breakpoints Sidebar saves time and helps you stay focused when debugging large apps.
+:::
+
+### Sidebar Structure
+
+The Breakpoints Sidebar displays all breakpoints grouped by file path and method or class name. For each breakpoint, the following information and actions are available:
+
+
+
+
+
Breakpoint label: Indicates the method or class and line number where the breakpoint is defined.
+
Status checkbox: Marks whether the breakpoint is currently active or disabled.
+
Line number: Shown on the right to indicate the exact position within the code.
+
Action icons: Provide quick access to delete, or navigate to a breakpoint once you hover over a breakpoint.
+
+
+
+
+
+
+
+:::info
+For more information, please refer to the [Managing Breakpoints](#managing-breakpoints) section.
+:::
+
+
+### Variables Panel
+
+During a debug session, the debugger sidebar also includes a **Variables section**. This panel helps you inspect the state of your code at any given moment by displaying:
+
+- **Local Variables**: Displays all variables that are currently in scope within the method or class being executed. This includes any variables you've defined inside that function or block of code.
+
+- **Current Line Variable**: Highlights the specific variable being accessed or modified on the line of code currently being executed.
+
+- **Arguments**: Lists the arguments passed into the method, function, or class. These represent the input values the current code is working with, as provided by the calling context.
+
+This makes it easier to understand the current context, trace issues, and test assumptions while stepping through your code.
+
+
+## Managing Breakpoints
+
+### Breakpoint Activation
+
+Each breakpoint is associated with an activation checkbox:
+
+- When the checkbox is selected , the breakpoint is active and will interrupt code execution during a debug session.
+- When the checkbox is cleared , the breakpoint remains listed but is temporarily disabled.
+
+To manage breakpoints in bulk, a "Disable All" button is available at the top of the panel, allowing all breakpoints across the app to be disabled simultaneously without deletion.
+
+### Breakpoint Deletion
+
+Breakpoints can be removed individually or in groups:
+
+- The trash icon beside a specific breakpoint removes only that breakpoint.
+- The trash icon beside a method or class header removes all breakpoints defined within that method or class.
+
+- An overflow menu (⋮) provides additional bulk removal options:
+
+
+
+
Remove Breakpoints in the Current File: Clears all breakpoints from your active file.
+
Remove Breakpoints in Other Files: Removes all breakpoints from non-active files.
+
Remove Breakpoints in All Files: Eliminates all breakpoints across the entire project.
+
+
+
+
+
+
+
+### Breakpoint Navigation
+
+Each breakpoint entry includes an edit icon that enables direct navigation to the corresponding line in the code editor. This function supports fast inspection or modification of the code surrounding the breakpoint location.
+
+
+### Collapse/Expand Groups
+
+To improve readability when working with a large number of breakpoints, the sidebar includes a "Collapse All" button . This feature collapses all method or class sections, minimizing the visual footprint of the list and helping developers focus only on relevant groups.
+
+
+## Using Qodly debugger on 4D Server
+
+When using Qodly pages in a deployed 4D Server application (interpreted mode), you might encounter some cases where you need to debug your pages on the server, for example when a specific user configuration is required. In this case, you can attach the Qodly Studio debugger to the 4D Server and then, benefit from its features when executing your Qodly pages.
+
+Note that in this case, the Qodly Studio debugger will display all the code executed on the server, in accordance with the [attached debugger rule on 4D Server](https://developer.4d.com/docs/Debugging/debugging-remote#attached-debugger).
+
+To attach the Qodly Studio debugger to your running 4D Server application:
+
+1. Open Qodly Studio from 4D Server.
+
+:::note
+
+The project must be running in interpreted mode so that **Qodly Studio** menu item is available.
+
+:::
+
+2. In the Qodly Studio toolbar, click on the **Debug** button.
+
+
+If the debug session starts successfully, a green bullet appears on the button label  and you can use the Qodly Studio debugger.
+
+If the debugger is already attached to a another machine or another Qodly Studio page, an error is displayed. You have to detach it beforehand from the other location.
+
+To detach the Qodly Studio debugger from your running 4D Server application:
+
+1. Click the **Debug** button in the Qodly Studio toolbar while a debug session is active.
+A warning dialog box will prompt you to confirm whether you want to detach the debugger.
+2. Select **Keep in progress** to continue evaluating the code until the end of the current method or function before detaching the debugger, or **Stop** to detach the debugger immediately.
+
diff --git a/versioned_docs/version-21/4DQodlyPro/deploy.md b/versioned_docs/version-21/4DQodlyPro/deploy.md
new file mode 100644
index 000000000..aef99dc35
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/deploy.md
@@ -0,0 +1,98 @@
+---
+id: deploy
+title: Deployment overview
+---
+
+
+## Enabling rendering
+
+Qodly Studio encapsulates Qodly pages, including layout, data connections, and event-driven logic, in a structured JSON file. This JSON file is processed on-the-fly by the **Qodly renderer** to serve a fully functional web page.
+
+:::info
+
+See [this page](../4DQodlyPro/rendering.md) for detailed information on how to render Qodly pages in Qodly.
+
+:::
+
+To enable the rendering of Qodly pages, the following options must be set.
+
+* The 4D project's **Settings** > **Web** > **Web Features** > [**Expose as REST server**](https://developer.4d.com/docs/19/REST/configuration#starting-the-rest-server) option must be activated.
+* The [4D web server](https://developer.4d.com/docs/WebServer/overview) must be running.
+
+:::note
+
+[Renderer buttons](../4DQodlyPro/rendering.md#page-rendering-options) are not available if the configuration options are not activated.
+
+:::
+
+## Scope of Qodly forms
+
+When rendering Qodly forms in the Qodly Studio, the renderer will connect to the 4D web server through HTTP or HTTPS, depending on the settings, following the same HTTP/HTTPS connection pattern as for the [4D WebAdmin web server](https://developer.4d.com/docs/Admin/webAdmin#accept-http-connections-on-localhost). See also [this paragraph](#about-license-usage-for-rendering) about URL schemes and license usage.
+
+
+Keep in mind that Qodly Studio runs through the 4D WebAdmin web server. When you use Qodly Studio as a developer, even when you preview a Qodly Page in the studio, you're using the 4D WebAdmin web server. This allows you to see dataclasses, functions and attributes that are not exposed as REST resources for example (they are greyed out).
+
+However, page rendering happens outside Qodly Studio, and is served by the standard 4D web server. In this situation, your web application cannot access assets that are not exposed as REST resources. See [Exposed vs non-exposed functions](https://developer.4d.com/docs/ORDA/ordaClasses#exposed-vs-non-exposed-functions) and [Exposing tables](https://developer.4d.com/docs/REST/configuration#exposing-tables) for more information on how to expose assets.
+
+
+
+
+## Accessing Qodly pages
+
+For deployment, the WebAdmin server is not necessary. End-user access to your web application made with Qodly Studio is based on the 4D REST protocol, and as such, it works as through a conventional 4D remote application.
+
+Your Qodly pages are available through the following url:
+
+```
+IP:port/$lib/renderer/?w=QodlyPageName
+```
+
+...where *IP:port* represents the address of the web server and *QodlyPageName* is the name of the Qodly page.
+
+For example:
+
+```
+https://www.myWebSite.com/$lib/renderer/?w=welcome
+```
+
+## Preview Qodly Application
+
+You can preview your Qodly application at any moment by selecting the **Preview Qodly Application...** command in the **Windows** menu (4D Server) or in the **Design** menu (4D single-user).
+
+This command launches the Qodly renderer on a local address in your default browser and displays the **start page** [defined in the Application settings](https://developer.qodly.com/docs/studio/settings#start-page) of Qodly Studio.
+
+
+
+
+## Using Qodly debugger on 4D Server
+
+When using Qodly pages in a deployed 4D Server application (interpreted mode), you might encounter some cases where you need to debug your pages on the server, for example when a specific user configuration is required. In this case, you can attach the [Qodly Studio debugger](https://developer.qodly.com/docs/studio/debugging) to the 4D Server and then, benefit from its features when executing your Qodly pages.
+
+Note that in this case, the Qodly Studio debugger will display all the code executed on the server, in accordance with the [attached debugger rule on 4D Server](https://developer.4d.com/docs/Debugging/debugging-remote#attached-debugger).
+
+To attach the Qodly Studio debugger to your running 4D Server application:
+
+1. [Open Qodly Studio](#opening-qodly-studio) from 4D Server.
+
+:::note
+
+The project must be running in interpreted mode so that **Qodly Studio** menu item is available.
+
+:::
+
+2. In the Qodly Studio toolbar, click on the **Debug** button.
+
+
+If the debug session starts successfully, a green bullet appears on the button label  and you can use the Qodly Studio debugger.
+
+If the debugger is already attached to a another machine or another Qodly Studio page, an error is displayed. You have to detach it beforehand from the other location.
+
+To detach the Qodly Studio debugger from your running 4D Server application:
+
+1. Click the **Debug** button in the Qodly Studio toolbar while a debug session is active.
+A warning dialog box will prompt you to confirm whether you want to detach the debugger.
+2. Select **Keep in progress** to continue evaluating the code until the end of the current method or function before detaching the debugger, or **Stop** to detach the debugger immediately.
+
+
+
+
diff --git a/versioned_docs/version-21/4DQodlyPro/force-login.md b/versioned_docs/version-21/4DQodlyPro/force-login.md
new file mode 100644
index 000000000..9df5d8c6a
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/force-login.md
@@ -0,0 +1,96 @@
+---
+id: force-login
+title: Force Login
+---
+
+
+
+With Qodly Studio for 4D, the ["force login" mode](https://developer.4d.com/docs/REST/authUsers#force-login-mode) allows you to control the number of opened web sessions that require 4D Client licenses. You can also [logout](#logout) the user at any moment to decrement the number of retained licenses.
+
+## Configuration
+
+Make sure the ["force login" mode](https://developer.4d.com/docs/REST/authUsers#force-login-mode) is enabled for your 4D application in the [Roles and Privileges page](https://developer.qodly.com/docs/studio/roles/rolesPrivilegesOverview), using the **Force login** option:
+
+
+
+You can also set this option directly in the [**roles.json** file](https://developer.4d.com/docs/ORDA/privileges#rolesjson-file).
+
+You just need then to implemented the [`authentify()`](https://developer.4d.com/docs/REST/authUsers#function-authentify) function in the datastore class and call it from the Qodly page. A licence will be consumed only when the user is actually logged.
+
+
+:::note Compatibility
+
+When the legacy login mode ([deprecated as of 4D 20 R6](https://blog.4d.com/force-login-becomes-default-for-all-rest-auth)) is enabled, any REST request, including the rendering of an authentication Qodly page, creates a web session on the server and gets a 4D Client license, whatever the actual result of the authentication. For more information, refer to [this blog post](https://blog.4d.com/improved-4d-client-licenses-usage-with-qodly-studio-for-4d) that tells the full story.
+
+:::
+
+### Example
+
+In a simple Qodly page with login/password inputs, a "Submit" button calls the following `authentify()` function we have implemented in the DataStore class:
+
+
+
+ ```4d
+ exposed Function authentify($credentials : Object) : Text
+ var $salesPersons : cs.SalesPersonsSelection
+ var $sp : cs.SalesPersonsEntity
+
+ $salesPersons:=ds.SalesPersons.query("identifier = :1"; $credentials.identifier)
+ $sp:=$salesPersons.first()
+
+ If ($sp#Null)
+ If (Verify password hash($credentials.password; $sp.password))
+ Session.clearPrivileges()
+ Session.setPrivileges("") //guest session
+
+ return "Authentication successful"
+ Else
+ return "Wrong password"
+ End if
+ Else
+ return "Wrong user"
+ End if
+ ```
+
+
+ ```qs
+ exposed function authentify(credentials : Object) : string
+ var salesPersons : cs.SalesPersonsSelection
+ var sp : cs.SalesPersonsEntity
+
+ salesPersons=ds.SalesPersons.query("identifier = :1", credentials.identifier)
+ sp = salesPersons.first()
+
+ if (sp!=Null)
+ if (verifyPasswordHash(credentials.password, sp.password))
+ session.clearPrivileges()
+ session.setPrivileges("") //guest session
+
+ return "Authentication successful"
+ else
+ return "Wrong password"
+ end
+ else
+ return "Wrong user"
+ end
+ ```
+
+
+
+This call is accepted and as long as the authentication is not successful, `Session.setPrivileges()` is not called, thus no license is consumed. Once `Session.setPrivileges()` is called, a 4D client licence is used and any REST request is then accepted.
+
+
+
+## Logout
+
+When the ["force login" mode is enabled](#force-login), Qodly Studio for 4D allows you to implement a logout feature in your application.
+
+To logout the user, you just need to execute the **Logout** standard action from the Qodly page. In Qodly Studio, you can associate this standard action to a button for example:
+
+
+
+Triggering the logout action from a web user session has the following effects:
+
+- the current web user session loses its privileges, only [descriptive REST requests](https://developer.4d.com/docs/REST/authUsers#descriptive-rest-requests) are allowed,
+- the associated 4D license is released,
+- the `Session.storage` is kept until the web session inactivity timeout is reached (at least one hour). During this period after a logout, if the user logs in again, the same session is used and the `Session.storage` shared object is available with its current contents.
diff --git a/versioned_docs/version-21/4DQodlyPro/gettingStarted.md b/versioned_docs/version-21/4DQodlyPro/gettingStarted.md
new file mode 100644
index 000000000..40f40045f
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/gettingStarted.md
@@ -0,0 +1,226 @@
+---
+id: gettingStarted
+title: Welcome
+---
+
+**Qodly Studio** is an interface builder for web applications. It provides developers with a graphical page editor to design applications running in web browsers or smartphones. It supports natively the [ORDA objects](https://developer.4d.com/docs/ORDA/overview).
+
+You can use Qodly Studio directly from your **4D environment** to build modern and sophisticated interfaces that you can easily integrate to your existing 4D projects and deploy **on premise**.
+
+Qodly Studio can also be used in the [**Qodly Cloud platform**](https://qodly.com), dedicated to the development of web business applications (see the [**Qodly Cloud**](../QodlyinCloud/quickstart.md) section of the documentation for more information).
+
+Qodly Studio proposes a full-featured web UI, allowing you to:
+
+- create Qodly pages by placing components on a canvas
+- map components to Qodly Sources
+- trigger 4D code by configuring events
+- and much more.
+
+
+## Configuration
+
+### Requirements
+
+#### Browser
+
+Qodly Studio supports the following web browsers:
+
+- Chrome
+- Edge
+- FireFox
+
+The recommended resolution is 1920x1080.
+
+
+#### Project
+
+Qodly Studio only works with 4D [projects](https://developer.4d.com/docs/Project/overview) (binary databases are not supported).
+
+- Web sessions (*aka* Scalable sessions) must [be enabled](https://developer.4d.com/docs/WebServer/sessions#enabling-web-sessions).
+- The ["forceLogin" mode](https://developer.4d.com/docs/REST/authUsers#force-login-mode) must be [activated](https://developer.4d.com/docs/settings/web#activate-rest-authentication-through-dsauthentify-function) to handle web sessions.
+- The 4D code called by Qodly forms must be [thread-safe](https://developer.4d.com/docs/WebServer/preemptiveWeb).
+
+
+#### Web Server & WebAdmin Server
+
+Qodly Studio is served by the [WebAdmin web server](https://developer.4d.com/docs/Admin/webAdmin) and access data from 4D projects exposed as [REST servers](https://developer.4d.com/docs/REST/configuration) and handled by the [4D web server](https://developer.4d.com/docs/WebServer/overview). **All these servers must be launched**. If one of these levels are not enabled, access to Qodly Studio is denied (a 403 page is returned).
+
+
+
+You need to [**enable access to Qodly Studio** on the WebAdmin web server](https://developer.4d.com/docs/Admin/webAdmin#enable-access-to-qodly-studio). This setting applies to the 4D application (4D or 4D Server) on the host machine. All projects opened with that 4D application take this setting into account.
+
+In addition, you need to explicitly designate every project that can be accessed. The **Enable access to Qodly Studio** option must be enabled on the [Web Features page of the 4D application's Settings](https://developer.4d.com/docs/settings/web#enable-access-to-qodly-studio). Keep in mind that [user settings](https://developer.4d.com/docs/settings/overview) can be defined at several levels, and that priorities apply.
+
+
+### One-click configuration
+
+All the [configuration requirements](#requirements) can be automatically set for you in one click when you select the **Qodly Studio...** menu command from the **Design** menu in 4D single-user for the first time. Any requirements that are not met are listed in a dialog box and will be automatically adjusted if you click the **Enable settings** button.
+
+
+
+:::note
+
+- Only settings that need to be edited are listed in the dialog box.
+- Since scalable sessions run in preemptive mode, enabling this setting might require that you evaluate the [thread-safety property](https://developer.4d.com/docs/Develop/preemptive-processes#writing-a-thread-safe-method) of your code.
+- Activating the "forceLogin" mode might require that you reconfigure the REST accesses, [as explained in this blog post](https://blog.4d.com/force-login-becomes-default-for-all-rest-auth/).
+
+:::
+
+
+### Activating authentication
+
+Authentication on the WebAdmin web server is granted using an access key. For more details, see [Access key](https://developer.4d.com/docs/Admin/webAdmin#access-key).
+
+In case of [access through 4D](#from-the-4d-application), an access key is transparently provided.
+
+
+### Development and deployment
+
+In accordance with the management of 4D projects, only the following usages are supported:
+
+- development with Qodly Studio must be done using **4D** (single-user).
+- deployment of 4D applications powered with Qodly pages must be done using **4D Server**.
+
+:::warning
+
+You can open Qodly Studio, [debug](#using-qodly-debugger-on-4d-server) and edit Qodly pages directly on a 4D Server machine when a project is running in interpreted mode. This feature is only provided for testing and debugging purposes, for example to evaluate the application flow with actual data, or in multi-user environment. It must NOT be considered as a regular way to develop applications since it does not provide any control over concurrent accesses.
+
+:::
+
+
+
+## Opening Qodly Studio
+
+The Qodly Studio page is available when [all requirements](#requirements) are met.
+
+There are two ways to access Qodly Studio:
+
+- by selecting the **Qodly Studio...** menu command from your 4D application,
+- by entering directly an url in a browser.
+
+### From a 4D application
+
+Select the **Qodly Studio...** menu command from the **Design** menu (4D single-user) or the **Window** menu (4D Server).
+
+Depending on the WebAdmin web server configuration, your default browser opens at `IPaddress:HTTPPort/studio` or `IPaddress:HTTPSPort/studio`.
+
+:::note
+
+When opening Qodly Studio from your 4D single-user application for the first time, you can benefit from the [one-click configuration dialog box](#one-click-configuration) to automatically configure all necessary settings.
+
+:::
+
+
+
+### On a browser
+
+When opening Qodly Studio on a browser, you need to make sure all necessary [requirements](#requirements) have been configured.
+
+with the WebAdmin web server running (launched from 4D or 4D Server), enter the following address:
+
+```
+IPaddress:HTTPPort/studio
+```
+or:
+
+```
+IPaddress:HTTPSPort/studio
+```
+
+For example, after launching a local web server on port 7080, type this address in your browser:
+
+`localhost:7080/studio`
+
+You will then be prompted to enter the [access key](https://developer.4d.com/docs/Admin/webAdmin#access-key) to access Qodly Studio.
+
+
+
+## Developing with Qodly Studio
+
+### Language
+
+The following commands and classes are dedicated to the server-side management of Qodly pages:
+
+- [`Web Form`](../QodlyinCloud/qodlyScript/commands/webForm.md) command: returns the Qodly page as an object.
+- [`Web Event`](../QodlyinCloud/qodlyScript/commands/webEvent.md) command: returns events triggered within Qodly page components.
+- [`WebForm`](../QodlyinCloud/qodlyScript/WebFormClass.md) class: functions and properties to manage the rendered Qodly page.
+- [`WebFormItem`](../QodlyinCloud/qodlyScript/WebFormItemClass.md) class: functions and properties to manage Qodly page components.
+
+
+### Using project methods
+
+We recommend using class functions over project methods. Only class functions can be called from [components](../4DQodlyPro/pageLoaders/components/componentsBasics.md). However, you can still use your project methods in Qodly Studio in two ways:
+
+- You can call your methods from class functions.
+- You can directly [execute your methods](../4DQodlyPro/coding.md#method-and-function-management) from the Qodly Explorer.
+
+
+### Offline use
+
+You can develop with Qodly Studio while your computer is not connected to the internet. In this case however, the following features are not available:
+
+- [Templates](../4DQodlyPro/pageLoaders/templates.md): the Template library is empty
+- UI tips: they are not displayed when you click on  icons.
+
+
+
+
+
+## Enabling rendering
+
+Qodly Studio encapsulates Qodly pages, including layout, data connections, and event-driven logic, in a structured JSON file. This JSON file is processed on-the-fly by the **Qodly renderer** to serve a fully functional web page.
+
+:::info
+
+See [this section](../4DQodlyPro/rendering.md) for detailed information on how to render Qodly pages in Qodly.
+
+:::
+
+To enable the rendering of Qodly pages, the following options must be set.
+
+* The 4D project's **Settings** > **Web** > **Web Features** > [**Expose as REST server**](https://developer.4d.com/docs/19/REST/configuration#starting-the-rest-server) option must be activated.
+* The [4D web server](https://developer.4d.com/docs/WebServer/overview) must be running.
+
+:::note
+
+[Renderer buttons](../4DQodlyPro/rendering.md#page-rendering-options) are not available if the configuration options are not activated.
+
+:::
+
+### Scope of Qodly forms
+
+When rendering Qodly forms in the Qodly Studio, the renderer will connect to the 4D web server through HTTP or HTTPS, depending on the settings, following the same HTTP/HTTPS connection pattern as for the [4D WebAdmin web server](https://developer.4d.com/docs/Admin/webAdmin#accept-http-connections-on-localhost). See also [this paragraph](#about-license-usage-for-rendering) about URL schemes and license usage.
+
+
+Keep in mind that Qodly Studio runs through the 4D WebAdmin web server. When you use Qodly Studio as a developer, even when you preview a Qodly Page in the studio, you're using the 4D WebAdmin web server. This allows you to see dataclasses, functions and attributes that are not exposed as REST resources for example (they are greyed out).
+
+However, page rendering happens outside Qodly Studio, and is served by the standard 4D web server. In this situation, your web application cannot access assets that are not exposed as REST resources. See [Exposed vs non-exposed functions](https://developer.4d.com/docs/ORDA/ordaClasses#exposed-vs-non-exposed-functions) and [Exposing tables](https://developer.4d.com/docs/REST/configuration#exposing-tables) for more information on how to expose assets.
+
+
+
+
+### Accessing Qodly pages
+
+For deployment, the WebAdmin server is not necessary. End-user access to your web application made with Qodly Studio is based on the 4D REST protocol, and as such, it works as through a conventional 4D remote application.
+
+Your Qodly pages are available through the following url:
+
+```
+IP:port/$lib/renderer/?w=QodlyPageName
+```
+
+...where *IP:port* represents the address of the web server and *QodlyPageName* is the name of the Qodly page.
+
+For example:
+
+```
+https://www.myWebSite.com/$lib/renderer/?w=welcome
+```
+
+### Preview Qodly Application
+
+You can preview your Qodly application at any moment by selecting the **Preview Qodly Application...** command in the **Windows** menu (4D Server) or in the **Design** menu (4D single-user).
+
+This command launches the Qodly renderer on a local address in your default browser and displays the **start page** [defined in the Application settings](https://developer.qodly.com/docs/studio/settings#start-page) of Qodly Studio.
+
+
diff --git a/versioned_docs/version-21/4DQodlyPro/httpHandlers.md b/versioned_docs/version-21/4DQodlyPro/httpHandlers.md
new file mode 100644
index 000000000..14bf3fccf
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/httpHandlers.md
@@ -0,0 +1,473 @@
+---
+id: httpHandlers
+title: HTTP Handlers
+---
+
+import Column from '@site/src/components/Column'
+
+## Overview
+
+HTTP Request Handlers let you define how your application responds to HTTP requests by:
+
+- Listening for specific URL patterns (e.g., /api/users).
+
+- Handling different HTTP methods (e.g., GET, POST, PUT).
+
+- Linking these requests to specific logic in your application.
+
+
+## Why Use HTTP Request Handlers?
+
+HTTP Request Handlers are ideal when:
+
+- **Server-Side Logic is Required**: Use HTTP Request Handlers to process server-side operations, such as interacting with databases, performing complex calculations, or handling business rules.
+
+ :::info Example
+ Processing user authentication or generating reports dynamically.
+ :::
+
+- **API-Like Behavior is Needed**: HTTP Request Handlers allow you to create RESTful endpoints that external systems or frontend applications can call.
+
+ :::info Example
+ A mobile app might send a POST request to /api/login to authenticate a user.
+ :::
+
+- **Scalable Interactions Across Multiple Clients**: Unlike UI events (which are specific to the client session), HTTP Request Handlers work server-side and can handle multiple client requests simultaneously.
+
+ :::info Example
+ Processing orders submitted from multiple web users simultaneously.
+ :::
+
+- **Custom URL Patterns and Verbs**: Handlers let you define flexible URL patterns and handle different HTTP methods like GET or POST for the same URL.
+
+ :::info Example
+ Using GET /products for listing products and POST /products for adding a new product.
+ :::
+
+
+:::tip When NOT to use HTTP Request Handlers:
+
+- **For purely UI-based interactions**, such as button clicks or navigating between pages in your app. These should be handled by front-end navigation events or on-click handlers.
+
+- **For real-time interactions**, like live updates to a page. Consider using WebSockets or a similar real-time communication mechanism instead.
+:::
+
+
+
+## Accessing HTTP Handlers
+
+HTTP Handlers can be accessed and managed in Qodly Studio via the HTTP Handlers menu in the Explorer:
+
+ 1. Locate the HTTP Handlers menu in the Explorer.
+
+ 2. If the menu item is greyed out , click it to create the HTTPHandlers.json file.
+
+ 3. Once created, the file will open for editing .
+
+
+
+ By default, the HTTP Handlers UI is displayed instead of the code editor, allowing handlers to be managed via an intuitive graphical interface.
+
+
+
+
+
+
+
+:::tip Switching Between UI and Code Edito
+
+Handlers can be modified using either the UI or the JSON editor.
+
+- To switch to the code editor view, clicking the code editor icon is required.
+
+- To return to the HTTP Handler UI, clicking the UI icon is necessary.
+
+- Changes made in one view are automatically synchronized with the other.
+
+:::
+
+
+If no handlers are configured, a message appears stating: `There are no HTTP Handlers configured yet. Click on the ➕ to add a new one.`.
+
+
+## Configuring HTTP Handlers
+
+### Add HTTP Handlers
+
+HTTP Handlers can be added through both the UI and the code editor.
+
+#### Using the HTTP Handler UI:
+
+1. Clicking the button opens a form for inputting handler details.
+
+2. The form requires specifying the following details:
+
+ - Class: Selecting an existing class from the dropdown list or manually entering a class name.
+ - Method: Choosing an available method from the selected class or entering a method manually.
+ - Pattern: Defining the URL pattern.
+ - As Regex: Enabling this if using a regular expression for URL matching.
+ - Verbs: Selecting supported HTTP methods (GET, POST, PUT, DELETE).
+
+
+
+3. Clicking "Confirm Changes" applies the configuration.
+
+:::danger Handling Class and Method Errors
+
+When manually entering a class name or method that does not exist, the system provides visual feedback to prevent misconfiguration.
+
+
+
+
+
If the specified class or method does not exist, a Nothing found message appears, and the input field turns red.
+
Unrecognized entries are automatically removed when pressing Enter.
+
+
+
+
+
+
+
+:::
+
+:::info Adding an Unsupported HTTP verb
+
+Only the four HTTP methods (GET, POST, PUT, DELETE) are supported. If an invalid method is typed and Enter is pressed, it will not be added.
+
+In contrast, supported methods (GET, POST, PUT, DELETE) will have a dark grey background to indicate they are valid.
+
+:::
+
+#### Using the Code Editor:
+
+Handlers can also be configured manually in the code editor, like:
+
+```json
+[
+ {
+ "class": "CustomerHandler",
+ "method": "getCustomer",
+ "pattern": "customers",
+ "verbs": "GET"
+ },
+ {
+ "class": "AuthHandler",
+ "method": "login",
+ "regexPattern": "/auth/login",
+ "verbs": "POST"
+ }
+]
+```
+
+
+:::danger Error Handling When Switching Views
+
+
+
+ If a class or method that does not exist is added directly through the code editor, there is no immediate validation or error handling. However, when switching to the UI view, the system detects invalid entries and highlights them in red. An error message appears below the affected fields, stating: `The singleton "Class or Method name" doesn't exist.`
+
+
+
+
+
+:::
+
+:::info Adding an Unsupported HTTP verb
+
+The code editor does not enforce immediate validation when an invalid HTTP method is added. If an unsupported HTTP method is entered in the JSON file, it will be saved without an error. However, when switching to the UI view the invalid method will be highlighted with a dark red background , indicating that it is not a valid option.
+
+:::
+
+### Edit HTTP Handlers
+
+Editing allows modifications to the class, method, URL pattern, or HTTP methods:
+
+- Use the UI to edit a handler and adjust its settings.
+
+- Or, edit the JSON file directly for manual changes from the code editor.
+
+### Delete HTTP Handlers
+
+Removing unnecessary handlers keeps request processing efficient:
+
+- Click the trash icon in the UI to delete a handler, then confirm the action.
+
+- Alternatively, delete the handler entry from the code editor.
+
+
+### Duplicate Handlers
+
+Duplicating handlers allows for quick replication:
+
+- Click the duplicate icon in the UI.
+
+- Copy and paste JSON objects for manual duplication.
+
+
+### Reorder Handlers
+
+Execution order can be adjusted:
+
+- Drag and drop handlers in the UI .
+
+- Manually rearrange JSON objects in the file.
+
+
+## Matching Rules
+
+Handlers can define URL patterns in two ways:
+
+- **Prefix (pattern)**: Matches any URL that starts with the specified prefix.
+
+ :::info Example
+ A handler with "pattern": `products` matches /products, /products/list, and /products/123.
+ :::
+
+- **Regular Expression (regexPattern)**: Matches URLs that satisfy the specified regular expression.
+
+ :::info Example
+ A handler with "regexPattern": `/products/[0-9]+` matches /products/123 and /products/456, but not /products/list.
+ :::
+
+
+:::tip Matching Scenarios
+
+Given the following configuration:
+
+```json
+[
+ {
+ "class": "OrderHandler",
+ "method": "getOrder",
+ "regexPattern": "/orders/[0-9]+",
+ "verbs": "GET"
+ },
+ {
+ "class": "OrderHandler",
+ "method": "listOrders",
+ "pattern": "orders",
+ "verbs": "GET"
+ }
+]
+```
+
+- `GET /orders/123`: Matches the first handler (getOrder) because `/orders/123` satisfies the regular expression `/orders/[0-9]+`.
+
+- `GET /orders`: Matches the second handler (listOrders) because `/orders` is a prefix of the URL.
+
+- `POST /orders/123`: Does not match any handler because neither handler supports the POST method.
+
+:::
+
+
+## Order of Execution
+
+When an HTTP request is received, the system evaluates each handler in the order they appear in the HTTPHandlers.json file or the UI. The execution flow follows these steps:
+
+- The URL of the request is matched against the pattern or regexPattern of each handler.
+
+- The HTTP method (verb) of the request is checked against the verbs property of the handler.
+
+- Only the first matching handler is executed, even if subsequent handlers also match the request.
+
+:::tip Example of First Match Execution
+
+Given the following configuration:
+
+
+
+
+
+ A request to GET `/products/123` will match both handlers:
+ - The first handler matches because the URL `/products/123` satisfies the regular expression `/products/[0-9]+`.
+ - The second handler matches because `/products` is a prefix of the URL.
+
+
+ ```json
+ [
+ {
+ "class": "ProductHandler",
+ "method": "getProductDetails",
+ "verbs": "GET",
+ "id": "2jSeKR6tDs4KTKXrH3vVzR",
+ "regexPattern": "/products/[0-9]+"
+ },
+ {
+ "class": "ProductHandler",
+ "method": "listProducts",
+ "verbs": "GET",
+ "id": "uJScfkSR6rkhoJSpfEiG6U",
+ "pattern": "products"
+ }
+ ]
+ ```
+
+
+
+
+
+
+
+However, only the first handler `getProductDetails` is executed, as it appears first in the configuration.
+
+:::
+
+
+## Request Handler Code
+
+### Function Configuration
+
+The HTTP Request handler code must be implemented in a function of a [**Shared**](../QodlyinCloud/qodlyScript/basics/lang-classes.md#shared-singleton) [**singleton class**](../QodlyinCloud/qodlyScript/basics/lang-classes.md#singleton-classes).
+
+If the singleton is missing or not shared, an error `Cannot find singleton` is returned by the server. If the class or the function defined as handler is not found, an error `Cannot find singleton function` is returned by the server.
+
+Request handler functions are not necessarily shared, unless some request handler properties are updated by the functions. In this case, you need to declare its functions with the [`shared` keyword](../QodlyinCloud/qodlyScript/basics/lang-classes.md#shared-functions).
+
+:::note
+
+It is **not recommended** to expose request handler functions to external REST calls using [`exposed`](../QodlyinCloud/qodlyScript/guides/data-model.md#exposed-vs-non-exposed-functions) or [`onHttpGet`](../QodlyinCloud/qodlyScript/guides/data-model.md#onhttpget-keyword) keywords.
+
+:::
+
+### Handling Errors
+
+If no handler matches a request, the system automatically returns a 404 error. This behavior ensures that unexpected or unsupported requests do not disrupt the application.
+
+To handle unmatched requests explicitly, you can define a fallback handler. Fallback handlers can be used to return custom error messages, redirect requests, or log unexpected behavior.
+
+```json
+{
+ "class": "ErrorHandler",
+ "method": "handleNotFound",
+ "regexPattern": "/.+",
+ "verbs": "*"
+}
+```
+
+This handler catches all requests that are not matched by earlier handlers. It can be used to:
+
+- Return a custom 404 page.
+
+- Log details about unmatched requests.
+
+- Redirect users to a default page.
+
+### Input: 4D.IncomingMessage Class Instance
+
+When a request is intercepted by the handler, it is received on the server as an instance of the [4D.IncomingMessage class](../QodlyinCloud/qodlyScript/IncomingMessageClass.md).
+
+This object provides all necessary request information, including the request URL, verb, headers, URL parameters, and request body. The request handler can use this information to trigger the appropriate business logic.
+
+### Output: 4D.OutgoingMessage Class Instance
+
+The request handler can return an object instance of the [4D.OutGoingMessage class](../QodlyinCloud/qodlyScript/OutgoingMessageClass.md), which represents web content ready for a browser to handle, such as a file content or a structured response.
+
+
+
+### Example
+
+The [4D.IncomingMessage class](../QodlyinCloud/qodlyScript/IncomingMessageClass.md) provides functions to retrieve [headers](../QodlyinCloud/qodlyScript/IncomingMessageClass.md#headers) and the [body](../QodlyinCloud/qodlyScript/IncomingMessageClass.md#gettext) of the request.
+
+Below is an example of an HTTP handler that processes a file upload.
+
+#### HTTP Request Handler Configuration:
+```json
+[
+ {
+ "class": "UploadFile",
+ "method": "uploadFile",
+ "regexPattern": "/putFile",
+ "verbs": "POST"
+ }
+]
+```
+
+The request URL: `/putFile?fileName=testFile`
+
+ - The binary content of the file is sent in the request body.
+
+ - The POST verb is used.
+
+ - The filename is included as a query parameter (*fileName*).
+
+ - The filename is extracted using the [`urlQuery`](../QodlyinCloud/qodlyScript/IncomingMessageClass.md#urlquery) object.
+
+
+#### Implementation of the UploadFile Class:
+
+
+
+ ```4d
+ //UploadFile class
+
+ shared singleton constructor()
+
+ Function uploadFile($request : 4D.IncomingMessage) : 4D.OutgoingMessage
+ var $response := 4D.OutgoingMessage.new()
+ var $body := "Not supported file"
+ var $fileName; fileType : Text
+ var $file : 4D.File
+ var $picture : Picture
+ var $created : Boolean
+
+ $fileName := $request.urlQuery.fileName
+ $fileType := $request.getHeader("Content-Type")
+
+ Case of
+ : ($fileType = "application/pdf")
+ $file := File("/PACKAGE/Files/"+fileName+".pdf")
+ $created := $file.create()
+ $file.setContent($request.getBlob())
+ $body := "Upload OK - File size: "+string($file.size)
+
+ : ($fileType = "image/jpeg")
+ $file := File("/PACKAGE/Files/"+fileName+".jpg")
+ $picture := $request.getPicture()
+ $body := "Upload OK - Image size: "+string($file.size)
+ End case
+
+ $response.setBody($body)
+ $response.setHeader("Content-Type"; "text/plain")
+
+ return $response
+ ```
+
+
+ ```qs
+ //UploadFile class
+
+ shared singleton constructor()
+
+ function uploadFile(request : 4D.IncomingMessage) : 4D.OutgoingMessage
+ var response = 4D.OutgoingMessage.new()
+ var body = "Not supported file"
+ var fileName, fileType : string
+ var file : 4D.File
+ var picture : picture
+ var created : boolean
+
+ fileName = request.urlQuery.fileName
+ fileType = request.getHeader("Content-Type")
+
+ switch
+ : (fileType == "application/pdf")
+ file = file("/PACKAGE/Files/"+fileName+".pdf")
+ created = file.create()
+ file.setContent(request.getBlob())
+ body = "Upload OK - File size: "+string(file.size)
+
+ : (fileType == "image/jpeg")
+ file = file("/PACKAGE/Files/"+fileName+".jpg")
+ picture = request.getPicture()
+ body = "Upload OK - Image size: "+string(file.size)
+
+ end
+
+ response.setBody(body)
+ response.setHeader("Content-Type", "text/plain")
+
+ return response
+ ```
+
+
+
diff --git a/versioned_docs/version-21/4DQodlyPro/img/+New.png b/versioned_docs/version-21/4DQodlyPro/img/+New.png
new file mode 100644
index 000000000..0ba1d1694
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/+New.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/HTTPHandlersCreated.png b/versioned_docs/version-21/4DQodlyPro/img/HTTPHandlersCreated.png
new file mode 100644
index 000000000..7f3d98c3d
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/HTTPHandlersCreated.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/HTTPHandlersGreyed.png b/versioned_docs/version-21/4DQodlyPro/img/HTTPHandlersGreyed.png
new file mode 100644
index 000000000..ac729906a
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/HTTPHandlersGreyed.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/HTTPHandlersOrderExecution.png b/versioned_docs/version-21/4DQodlyPro/img/HTTPHandlersOrderExecution.png
new file mode 100644
index 000000000..8b478833b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/HTTPHandlersOrderExecution.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/HTTPHandlersUIInterface.png b/versioned_docs/version-21/4DQodlyPro/img/HTTPHandlersUIInterface.png
new file mode 100644
index 000000000..8ac97123c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/HTTPHandlersUIInterface.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/QodlyEvents1.png b/versioned_docs/version-21/4DQodlyPro/img/QodlyEvents1.png
new file mode 100644
index 000000000..f2eb72dd8
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/QodlyEvents1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/UnrecognizedHTTPHandlers.png b/versioned_docs/version-21/4DQodlyPro/img/UnrecognizedHTTPHandlers.png
new file mode 100644
index 000000000..e863a1a24
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/UnrecognizedHTTPHandlers.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/UnrecognizedHTTPHandlers2.png b/versioned_docs/version-21/4DQodlyPro/img/UnrecognizedHTTPHandlers2.png
new file mode 100644
index 000000000..cc7a4ed6d
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/UnrecognizedHTTPHandlers2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/UnrecognizedHTTPHandlers3.png b/versioned_docs/version-21/4DQodlyPro/img/UnrecognizedHTTPHandlers3.png
new file mode 100644
index 000000000..fef94c60f
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/UnrecognizedHTTPHandlers3.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/addHandlersButton.png b/versioned_docs/version-21/4DQodlyPro/img/addHandlersButton.png
new file mode 100644
index 000000000..abbde42b4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/addHandlersButton.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/addHandlersForm.png b/versioned_docs/version-21/4DQodlyPro/img/addHandlersForm.png
new file mode 100644
index 000000000..7c0ffbaa1
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/addHandlersForm.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/addLocalLocalization.png b/versioned_docs/version-21/4DQodlyPro/img/addLocalLocalization.png
new file mode 100644
index 000000000..054455744
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/addLocalLocalization.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/addLocalLocalization2.png b/versioned_docs/version-21/4DQodlyPro/img/addLocalLocalization2.png
new file mode 100644
index 000000000..25e2a038b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/addLocalLocalization2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/availableLocales.png b/versioned_docs/version-21/4DQodlyPro/img/availableLocales.png
new file mode 100644
index 000000000..320f976e3
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/availableLocales.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/codeEditorView.png b/versioned_docs/version-21/4DQodlyPro/img/codeEditorView.png
new file mode 100644
index 000000000..d2fb1ec11
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/codeEditorView.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/codingCollaborative_reload.png b/versioned_docs/version-21/4DQodlyPro/img/codingCollaborative_reload.png
new file mode 100644
index 000000000..e354c957b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/codingCollaborative_reload.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/codingCollaborative_saveAll.png b/versioned_docs/version-21/4DQodlyPro/img/codingCollaborative_saveAll.png
new file mode 100644
index 000000000..2b04e5083
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/codingCollaborative_saveAll.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/coding_Creating.png b/versioned_docs/version-21/4DQodlyPro/img/coding_Creating.png
new file mode 100644
index 000000000..af13c2958
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/coding_Creating.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/coding_delete.png b/versioned_docs/version-21/4DQodlyPro/img/coding_delete.png
new file mode 100644
index 000000000..c8dd35292
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/coding_delete.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/coding_delete2.png b/versioned_docs/version-21/4DQodlyPro/img/coding_delete2.png
new file mode 100644
index 000000000..768bebb03
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/coding_delete2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/coding_duplicate.png b/versioned_docs/version-21/4DQodlyPro/img/coding_duplicate.png
new file mode 100644
index 000000000..8c78c9bb1
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/coding_duplicate.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/coding_execute.png b/versioned_docs/version-21/4DQodlyPro/img/coding_execute.png
new file mode 100644
index 000000000..ab0571c1a
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/coding_execute.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/coding_execute2.png b/versioned_docs/version-21/4DQodlyPro/img/coding_execute2.png
new file mode 100644
index 000000000..f5c681091
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/coding_execute2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/coding_namingMethod.png b/versioned_docs/version-21/4DQodlyPro/img/coding_namingMethod.png
new file mode 100644
index 000000000..a452ef7db
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/coding_namingMethod.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/coding_palette.png b/versioned_docs/version-21/4DQodlyPro/img/coding_palette.png
new file mode 100644
index 000000000..68d4947cd
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/coding_palette.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/coding_palette2.png b/versioned_docs/version-21/4DQodlyPro/img/coding_palette2.png
new file mode 100644
index 000000000..b3a899d14
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/coding_palette2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/coding_reload.png b/versioned_docs/version-21/4DQodlyPro/img/coding_reload.png
new file mode 100644
index 000000000..f8965cee5
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/coding_reload.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/coding_rename.png b/versioned_docs/version-21/4DQodlyPro/img/coding_rename.png
new file mode 100644
index 000000000..bc9e99d35
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/coding_rename.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/coding_save.png b/versioned_docs/version-21/4DQodlyPro/img/coding_save.png
new file mode 100644
index 000000000..af1cd2c88
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/coding_save.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/coding_tabManagement.png b/versioned_docs/version-21/4DQodlyPro/img/coding_tabManagement.png
new file mode 100644
index 000000000..fdba26ee9
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/coding_tabManagement.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/confirmAddHandler.png b/versioned_docs/version-21/4DQodlyPro/img/confirmAddHandler.png
new file mode 100644
index 000000000..a4914c021
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/confirmAddHandler.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/createFromHomePage.png b/versioned_docs/version-21/4DQodlyPro/img/createFromHomePage.png
new file mode 100644
index 000000000..8fc5cde1b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/createFromHomePage.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/debug1.png b/versioned_docs/version-21/4DQodlyPro/img/debug1.png
new file mode 100644
index 000000000..8ca151119
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/debug1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/debug10.png b/versioned_docs/version-21/4DQodlyPro/img/debug10.png
new file mode 100644
index 000000000..b0921d48c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/debug10.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/debug11.png b/versioned_docs/version-21/4DQodlyPro/img/debug11.png
new file mode 100644
index 000000000..fb7400354
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/debug11.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/debug12.png b/versioned_docs/version-21/4DQodlyPro/img/debug12.png
new file mode 100644
index 000000000..4ce0eddde
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/debug12.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/debug2.png b/versioned_docs/version-21/4DQodlyPro/img/debug2.png
new file mode 100644
index 000000000..783ff6426
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/debug2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/debug3.png b/versioned_docs/version-21/4DQodlyPro/img/debug3.png
new file mode 100644
index 000000000..fe8429217
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/debug3.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/debug4.png b/versioned_docs/version-21/4DQodlyPro/img/debug4.png
new file mode 100644
index 000000000..79e500b73
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/debug4.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/debug5.png b/versioned_docs/version-21/4DQodlyPro/img/debug5.png
new file mode 100644
index 000000000..20643a1fa
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/debug5.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/debug6.png b/versioned_docs/version-21/4DQodlyPro/img/debug6.png
new file mode 100644
index 000000000..fada1f555
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/debug6.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/debug7.png b/versioned_docs/version-21/4DQodlyPro/img/debug7.png
new file mode 100644
index 000000000..7e64e0ed9
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/debug7.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/debug8.png b/versioned_docs/version-21/4DQodlyPro/img/debug8.png
new file mode 100644
index 000000000..025542b4b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/debug8.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/debug9.png b/versioned_docs/version-21/4DQodlyPro/img/debug9.png
new file mode 100644
index 000000000..913d1c955
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/debug9.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/delete.png b/versioned_docs/version-21/4DQodlyPro/img/delete.png
new file mode 100644
index 000000000..c1eefc795
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/delete.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/deleteKey.png b/versioned_docs/version-21/4DQodlyPro/img/deleteKey.png
new file mode 100644
index 000000000..f12f8fd9e
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/deleteKey.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/deleteLocaleButton.png b/versioned_docs/version-21/4DQodlyPro/img/deleteLocaleButton.png
new file mode 100644
index 000000000..87edfd039
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/deleteLocaleButton.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/dragDrop.png b/versioned_docs/version-21/4DQodlyPro/img/dragDrop.png
new file mode 100644
index 000000000..4ad4023bd
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/dragDrop.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/duplicate.png b/versioned_docs/version-21/4DQodlyPro/img/duplicate.png
new file mode 100644
index 000000000..4e9b83e73
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/duplicate.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/editHandler.png b/versioned_docs/version-21/4DQodlyPro/img/editHandler.png
new file mode 100644
index 000000000..5661affe4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/editHandler.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/editKey.png b/versioned_docs/version-21/4DQodlyPro/img/editKey.png
new file mode 100644
index 000000000..b040d0100
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/editKey.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/editLocaleButton.png b/versioned_docs/version-21/4DQodlyPro/img/editLocaleButton.png
new file mode 100644
index 000000000..eea70e187
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/editLocaleButton.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/enable-settings.png b/versioned_docs/version-21/4DQodlyPro/img/enable-settings.png
new file mode 100644
index 000000000..e5aea5bee
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/enable-settings.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/error-tip.png b/versioned_docs/version-21/4DQodlyPro/img/error-tip.png
new file mode 100644
index 000000000..bb493e86d
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/error-tip.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/error.png b/versioned_docs/version-21/4DQodlyPro/img/error.png
new file mode 100644
index 000000000..29aadd23f
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/error.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/explorerLocalization.png b/versioned_docs/version-21/4DQodlyPro/img/explorerLocalization.png
new file mode 100644
index 000000000..78052a9d9
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/explorerLocalization.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/exportLitralsButton.png b/versioned_docs/version-21/4DQodlyPro/img/exportLitralsButton.png
new file mode 100644
index 000000000..290c6b59f
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/exportLitralsButton.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/exportLitralsButton2.png b/versioned_docs/version-21/4DQodlyPro/img/exportLitralsButton2.png
new file mode 100644
index 000000000..314b96a2f
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/exportLitralsButton2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/exportLitralsCheckbox.png b/versioned_docs/version-21/4DQodlyPro/img/exportLitralsCheckbox.png
new file mode 100644
index 000000000..b7bc198be
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/exportLitralsCheckbox.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/exportLitralsCheckboxAll.png b/versioned_docs/version-21/4DQodlyPro/img/exportLitralsCheckboxAll.png
new file mode 100644
index 000000000..19b85ad0c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/exportLitralsCheckboxAll.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/exportLitralsModal.png b/versioned_docs/version-21/4DQodlyPro/img/exportLitralsModal.png
new file mode 100644
index 000000000..a3887551c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/exportLitralsModal.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/forcelogin.png b/versioned_docs/version-21/4DQodlyPro/img/forcelogin.png
new file mode 100644
index 000000000..b92d43570
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/forcelogin.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/helper-tip.png b/versioned_docs/version-21/4DQodlyPro/img/helper-tip.png
new file mode 100644
index 000000000..119654025
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/helper-tip.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/httpHandlerView.png b/versioned_docs/version-21/4DQodlyPro/img/httpHandlerView.png
new file mode 100644
index 000000000..d13ed7c09
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/httpHandlerView.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/importLitralsButton.png b/versioned_docs/version-21/4DQodlyPro/img/importLitralsButton.png
new file mode 100644
index 000000000..02f572f86
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/importLitralsButton.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/importLitralsModal.png b/versioned_docs/version-21/4DQodlyPro/img/importLitralsModal.png
new file mode 100644
index 000000000..e466130b0
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/importLitralsModal.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/invalidVerb.png b/versioned_docs/version-21/4DQodlyPro/img/invalidVerb.png
new file mode 100644
index 000000000..079d42bb4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/invalidVerb.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/localizationPageKeyLocale.png b/versioned_docs/version-21/4DQodlyPro/img/localizationPageKeyLocale.png
new file mode 100644
index 000000000..7663d390e
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/localizationPageKeyLocale.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/localizationPageKeyLocale2.png b/versioned_docs/version-21/4DQodlyPro/img/localizationPageKeyLocale2.png
new file mode 100644
index 000000000..b348f9993
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/localizationPageKeyLocale2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/localizationResult1.png b/versioned_docs/version-21/4DQodlyPro/img/localizationResult1.png
new file mode 100644
index 000000000..531d07574
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/localizationResult1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/localizationResult2.png b/versioned_docs/version-21/4DQodlyPro/img/localizationResult2.png
new file mode 100644
index 000000000..56d2a8ea4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/localizationResult2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/logout.png b/versioned_docs/version-21/4DQodlyPro/img/logout.png
new file mode 100644
index 000000000..5f78ffb83
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/logout.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/lsp.png b/versioned_docs/version-21/4DQodlyPro/img/lsp.png
new file mode 100644
index 000000000..a9cf6c096
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/lsp.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints1.png b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints1.png
new file mode 100644
index 000000000..89cf26359
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints10.png b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints10.png
new file mode 100644
index 000000000..f6452694e
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints10.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints11.png b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints11.png
new file mode 100644
index 000000000..07da312c0
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints11.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints12.png b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints12.png
new file mode 100644
index 000000000..e9853e379
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints12.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints2.png b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints2.png
new file mode 100644
index 000000000..b3883bc9d
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints3.png b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints3.png
new file mode 100644
index 000000000..933c4f449
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints3.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints4.png b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints4.png
new file mode 100644
index 000000000..d9d0c3327
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints4.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints5.png b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints5.png
new file mode 100644
index 000000000..08c81a5c7
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints5.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints6.png b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints6.png
new file mode 100644
index 000000000..5ef40229e
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints6.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints7.png b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints7.png
new file mode 100644
index 000000000..aafc385ba
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints7.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints8.png b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints8.png
new file mode 100644
index 000000000..ce1c9e17a
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints8.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints9.png b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints9.png
new file mode 100644
index 000000000..c6209dda2
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/managingBreakpoints9.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/manualEditLocal1.png b/versioned_docs/version-21/4DQodlyPro/img/manualEditLocal1.png
new file mode 100644
index 000000000..12fbb6b2b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/manualEditLocal1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/manualEditLocal2.png b/versioned_docs/version-21/4DQodlyPro/img/manualEditLocal2.png
new file mode 100644
index 000000000..a0f1ee6b4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/manualEditLocal2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/multipleSupportedLocales.png b/versioned_docs/version-21/4DQodlyPro/img/multipleSupportedLocales.png
new file mode 100644
index 000000000..c184b2cd1
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/multipleSupportedLocales.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/noLocalLocalization.png b/versioned_docs/version-21/4DQodlyPro/img/noLocalLocalization.png
new file mode 100644
index 000000000..100d4338f
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/noLocalLocalization.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/overview_model.png b/versioned_docs/version-21/4DQodlyPro/img/overview_model.png
new file mode 100644
index 000000000..786e6b232
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/overview_model.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/pageDefaultNoLocale.png b/versioned_docs/version-21/4DQodlyPro/img/pageDefaultNoLocale.png
new file mode 100644
index 000000000..e7d0c1e37
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/pageDefaultNoLocale.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/pageLocalizationButton.png b/versioned_docs/version-21/4DQodlyPro/img/pageLocalizationButton.png
new file mode 100644
index 000000000..ff4eb2ddb
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/pageLocalizationButton.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/preview-ide.png b/versioned_docs/version-21/4DQodlyPro/img/preview-ide.png
new file mode 100644
index 000000000..107428188
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/preview-ide.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/preview-main.png b/versioned_docs/version-21/4DQodlyPro/img/preview-main.png
new file mode 100644
index 000000000..2d852a01a
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/preview-main.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/preview-pop.png b/versioned_docs/version-21/4DQodlyPro/img/preview-pop.png
new file mode 100644
index 000000000..a93d5b452
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/preview-pop.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/preview-tab.png b/versioned_docs/version-21/4DQodlyPro/img/preview-tab.png
new file mode 100644
index 000000000..a1e801cef
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/preview-tab.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/primaryLocale.png b/versioned_docs/version-21/4DQodlyPro/img/primaryLocale.png
new file mode 100644
index 000000000..8bf4ec7b5
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/primaryLocale.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-classes.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-classes.png
new file mode 100644
index 000000000..a0109149c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-classes.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-debug.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-debug.png
new file mode 100644
index 000000000..88e2f7f2a
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-debug.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-classes-icon-explorer-object-structure.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-classes-icon-explorer-object-structure.png
new file mode 100644
index 000000000..fbfe1a8d2
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-classes-icon-explorer-object-structure.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-classes-icon-grid-create-new-class.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-classes-icon-grid-create-new-class.png
new file mode 100644
index 000000000..fbfe1a8d2
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-classes-icon-grid-create-new-class.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-data-icon-grid-access-data-explorer.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-data-icon-grid-access-data-explorer.png
new file mode 100644
index 000000000..5ba01e264
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-data-icon-grid-access-data-explorer.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-data-icon-headerbar-access-data-explorer.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-data-icon-headerbar-access-data-explorer.png
new file mode 100644
index 000000000..5ba01e264
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-data-icon-headerbar-access-data-explorer.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-debug-icon-headerbar-server-debugging.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-debug-icon-headerbar-server-debugging.png
new file mode 100644
index 000000000..ccf57f300
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-debug-icon-headerbar-server-debugging.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-debugger-icon-explorer-error-tools.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-debugger-icon-explorer-error-tools.png
new file mode 100644
index 000000000..0fed67c66
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-debugger-icon-explorer-error-tools.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-home-page-interface-explorer-header-grid.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-home-page-interface-explorer-header-grid.png
new file mode 100644
index 000000000..7f0845651
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-home-page-interface-explorer-header-grid.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-methods-icon-explorer-code-blocks.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-methods-icon-explorer-code-blocks.png
new file mode 100644
index 000000000..fbfe1a8d2
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-methods-icon-explorer-code-blocks.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-methods-icon-grid-create-new-method.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-methods-icon-grid-create-new-method.png
new file mode 100644
index 000000000..fbfe1a8d2
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-methods-icon-grid-create-new-method.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-model-icon-explorer-datastore-structure.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-model-icon-explorer-datastore-structure.png
new file mode 100644
index 000000000..786e6b232
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-model-icon-explorer-datastore-structure.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-open-tabs-title-bar-editor-navigation.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-open-tabs-title-bar-editor-navigation.png
new file mode 100644
index 000000000..a8b9c931c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-open-tabs-title-bar-editor-navigation.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-pages-icon-project-overview.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-pages-icon-project-overview.png
new file mode 100644
index 000000000..9a77de2b2
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-pages-icon-project-overview.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-preview-icon-headerbar-site-rendering.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-preview-icon-headerbar-site-rendering.png
new file mode 100644
index 000000000..42acba410
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-preview-icon-headerbar-site-rendering.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-preview-mode-tab-italic-label-indicator.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-preview-mode-tab-italic-label-indicator.png
new file mode 100644
index 000000000..323c6567c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-preview-mode-tab-italic-label-indicator.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-roles-privileges-icon-explorer-access-control.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-roles-privileges-icon-explorer-access-control.png
new file mode 100644
index 000000000..d549d4c60
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-roles-privileges-icon-explorer-access-control.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-save-all-icon-headerbar-app-wide-changes.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-save-all-icon-headerbar-app-wide-changes.png
new file mode 100644
index 000000000..4a5fd6164
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-save-all-icon-headerbar-app-wide-changes.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-settings-icon-explorer-app-configuration.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-settings-icon-explorer-app-configuration.png
new file mode 100644
index 000000000..d766cf06c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-settings-icon-explorer-app-configuration.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-settings-icon-headerbar-app-config-options.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-settings-icon-headerbar-app-config-options.png
new file mode 100644
index 000000000..d766cf06c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-settings-icon-headerbar-app-config-options.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-shared-icon-explorer-assets-folder.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-shared-icon-explorer-assets-folder.png
new file mode 100644
index 000000000..bbafc8f78
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-shared-icon-explorer-assets-folder.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-tab-context-menu-options-right-click.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-tab-context-menu-options-right-click.png
new file mode 100644
index 000000000..920756551
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-tab-context-menu-options-right-click.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-tab-scrollbar-overflow-navigation-editor-tabs.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-tab-scrollbar-overflow-navigation-editor-tabs.png
new file mode 100644
index 000000000..910363e62
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-tab-scrollbar-overflow-navigation-editor-tabs.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-unsaved-changes-warning-prompt-dialog.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-unsaved-changes-warning-prompt-dialog.png
new file mode 100644
index 000000000..16544a0ab
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-unsaved-changes-warning-prompt-dialog.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-unsaved-tab-indicator-modified-content.png b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-unsaved-tab-indicator-modified-content.png
new file mode 100644
index 000000000..3c74fbb13
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/qodly-studio-unsaved-tab-indicator-modified-content.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/render-button.png b/versioned_docs/version-21/4DQodlyPro/img/render-button.png
new file mode 100644
index 000000000..0c3f68da8
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/render-button.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/revokeI18nKey.png b/versioned_docs/version-21/4DQodlyPro/img/revokeI18nKey.png
new file mode 100644
index 000000000..bd431e3e0
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/revokeI18nKey.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/revokeI18nKeyButton.png b/versioned_docs/version-21/4DQodlyPro/img/revokeI18nKeyButton.png
new file mode 100644
index 000000000..5cf55a26b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/revokeI18nKeyButton.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/schemes.png b/versioned_docs/version-21/4DQodlyPro/img/schemes.png
new file mode 100644
index 000000000..705699955
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/schemes.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/settings-application.png b/versioned_docs/version-21/4DQodlyPro/img/settings-application.png
new file mode 100644
index 000000000..aa44dcf7b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/settings-application.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/settings-button.png b/versioned_docs/version-21/4DQodlyPro/img/settings-button.png
new file mode 100644
index 000000000..5806e8b86
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/settings-button.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/settings-user.png b/versioned_docs/version-21/4DQodlyPro/img/settings-user.png
new file mode 100644
index 000000000..2f6cf1154
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/settings-user.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/show-documentation.png b/versioned_docs/version-21/4DQodlyPro/img/show-documentation.png
new file mode 100644
index 000000000..1cec68a87
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/show-documentation.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/studioLocaleValue1.png b/versioned_docs/version-21/4DQodlyPro/img/studioLocaleValue1.png
new file mode 100644
index 000000000..15959015e
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/studioLocaleValue1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/studioLocaleValue2.png b/versioned_docs/version-21/4DQodlyPro/img/studioLocaleValue2.png
new file mode 100644
index 000000000..1f690b0de
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/studioLocaleValue2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/styleboxLanguageSwitching.png b/versioned_docs/version-21/4DQodlyPro/img/styleboxLanguageSwitching.png
new file mode 100644
index 000000000..d9598bcf9
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/styleboxLanguageSwitching.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/styleboxLanguageSwitching2.png b/versioned_docs/version-21/4DQodlyPro/img/styleboxLanguageSwitching2.png
new file mode 100644
index 000000000..770744247
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/styleboxLanguageSwitching2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/textTranslationKey.png b/versioned_docs/version-21/4DQodlyPro/img/textTranslationKey.png
new file mode 100644
index 000000000..99d8c9a27
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/textTranslationKey.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/tips.png b/versioned_docs/version-21/4DQodlyPro/img/tips.png
new file mode 100644
index 000000000..a3b553461
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/tips.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/tool-tip.png b/versioned_docs/version-21/4DQodlyPro/img/tool-tip.png
new file mode 100644
index 000000000..548fe586b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/tool-tip.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/tooltipI18n.png b/versioned_docs/version-21/4DQodlyPro/img/tooltipI18n.png
new file mode 100644
index 000000000..fec829c9c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/tooltipI18n.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/tooltipI18nModal.png b/versioned_docs/version-21/4DQodlyPro/img/tooltipI18nModal.png
new file mode 100644
index 000000000..ff421dc98
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/tooltipI18nModal.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/tooltipI18nModalCreateKey.png b/versioned_docs/version-21/4DQodlyPro/img/tooltipI18nModalCreateKey.png
new file mode 100644
index 000000000..c0cdf9762
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/tooltipI18nModalCreateKey.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/tooltipI18nModalSearchForKeys.png b/versioned_docs/version-21/4DQodlyPro/img/tooltipI18nModalSearchForKeys.png
new file mode 100644
index 000000000..9de591358
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/tooltipI18nModalSearchForKeys.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/validVerb.png b/versioned_docs/version-21/4DQodlyPro/img/validVerb.png
new file mode 100644
index 000000000..091e77de3
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/validVerb.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/warning.png b/versioned_docs/version-21/4DQodlyPro/img/warning.png
new file mode 100644
index 000000000..091493c93
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/warning.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/web-studio-intro.png b/versioned_docs/version-21/4DQodlyPro/img/web-studio-intro.png
new file mode 100644
index 000000000..1b525e940
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/web-studio-intro.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/img/workflow.png b/versioned_docs/version-21/4DQodlyPro/img/workflow.png
new file mode 100644
index 000000000..d89572d7a
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/img/workflow.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/license.md b/versioned_docs/version-21/4DQodlyPro/license.md
new file mode 100644
index 000000000..4dd8acdf9
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/license.md
@@ -0,0 +1,58 @@
+---
+id: license
+title: License usage for rendering
+---
+
+
+
+In default mode when any page is rendered, or in "force login" mode when a page handling data or calling a function is rendered, you must have an available license, as rendering Qodly forms targets the project database's main web server.
+
+## URL Schemes
+
+Qodly Studio's URL scheme configuration (HTTP and HTTPS) determines how many licenses are retained when rendering Qodly forms. With the appropriate configuration, you can avoid unnecessary license retaining.
+
+As explained in the [configuration](#configuration) section, the WebAdmin web server provides a secured web access to Qodly Studio. On the other hand, the [renderer](#enabling-rendering) communicates with the 4D web server of the database using REST requests. As such, it behaves like a conventional 4D Client.
+
+If you run the renderer from the Qodly Studio and these two web servers are not reached through the same URL scheme (HTTP or HTTPS), it might lead to wrong licence counting.
+
+:::info
+
+Using different schemes might also lead to [session](https://developer.4d.com/docs/commands/session) issues, such as losing [privileges](https://developer.4d.com/docs/ORDA/privileges) after a page refresh.
+
+:::
+
+### Example
+
+1. You run the Qodly Studio on an HTTPS URL scheme (e.g. `https://127.0.0.1:7443/studio/`)
+
+2. The web server of your database is started only on an HTTP port.
+
+
+
+3. In Qodly Studio, you click on the **Preview** icon. You are warned that the two web servers are started on different schemes, but despite this you click on the **Confirm** button.
+
+
+
+As a result, two licenses are retained.
+
+:::note
+
+You can enable/disable the display of the renderer pop over using a Qodly Studio user setting.
+
+:::
+
+## SameSite attribute
+
+The behavior previously described is due to the session cookie of the 4D web server. This session cookie has a `SameSite` attribute that determines if the session cookie is sent to the web server.
+
+If the `SameSite` attribute's value is `Strict` (default), the session cookie is not sent to the web server, so a new session is opened each time a page is rendered or refreshed.
+
+For more information on the `SameSite` attribute, check out [this blog post](https://blog.4d.com/get-ready-for-the-new-SameSite-and-secure-attributes-for-cookies/).
+
+## Recommendations
+
+To avoid using more licenses than necessary, we recommend doing one of the following:
+
+- Run the renderer on another browser tab (by entering the rendered URL of your Qodly page: `IP:port/$lib/renderer/?w=QodlyPageName`).
+- Ensure the Qodly Studio and your database are reached on the same URL scheme.
+- Use the `Lax` value for the [session cookie](https://developer.4d.com/docs/WebServer/webServerConfig.html#session-cookie-samesite) of your project database's web server.
\ No newline at end of file
diff --git a/versioned_docs/version-21/4DQodlyPro/localization.md b/versioned_docs/version-21/4DQodlyPro/localization.md
new file mode 100644
index 000000000..774362f0f
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/localization.md
@@ -0,0 +1,542 @@
+---
+id: localization
+title: Localization (i18n)
+---
+
+import Column from '@site/src/components/Column'
+
+## Overview
+
+The Localization (i18n) in Qodly provides built-in support for creating multilingual applications without requiring any manual coding.
+It enables developers to manage languages, create translation keys, configure dynamic translations for page elements, and allow end-users to switch languages at runtime.
+
+Localization in Qodly is designed to integrate seamlessly into the visual development workflow. Developers can define locales, manage literals (translated text values), and bind language-switching mechanisms using qodlysources, without needing to manipulate data structures manually.
+
+:::info Feature Capabilities
+- Define supported application locales.
+- Set a primary (default) locale for fallback.
+- Create and manage translation keys linked to UI components.
+- Edit literals per locale directly within the Webform Editor.
+- Enable runtime language switching through the UserLanguage shared source.
+- Export and import literals using CSV or JSON for external translation processes.
+:::
+
+## Key Concepts
+
+| Term | Definition |
+|---------------------------------|---------------------------------------------------------------|
+| Locale | A specific language or regional variation used for translations. |
+| Primary Locale | The fallback language displayed when no user selection or supported browser language is available. |
+| Literal | A piece of user-facing text that can have translations across multiple locales. |
+| Key | A unique reference identifier linking a literal to its translations. |
+| UserLanguage | A built-in shared source containing the currently selected locale and the list of supported locales. |
+
+
+## Localization Setup
+
+### Accessing the Localization Page
+
+
+
+ The **Localization** page provides a centralized interface for managing application languages and translation keys.
+
+ It is accessible through the **Explorer** view within Qodly Studio.
+
+ From this page, developers can create supported locales, define primary locales, manage literals, and configure translations without modifying application logic.
+
+
+
+
+
+
+
+### Creating Locales
+
+A **locale** represents a specific language or regional variant that the application supports. Adding locales allows the application to adapt its content to different audiences.
+
+To add supported languages:
+
+
+
+ 1. Navigate to the Localization page.
+
+ 2. Click **Add new Locale** to open the locale selection dropdown.
+
+
+
+
+
+
+
+
+ 3. Choose the desired language from the list.
+
+ 4. Validate the selection to add the locale.
+
+
+
+
+
+
+
+
+
+ Developers can define multiple supported locales by clicking on the "+" button .
+
+
+
+
+
+
+
+### Setting the Primary Locale
+
+One of the locales must be set as the **Primary Locale**, which acts as the default language whenever no user preference or browser match is available.
+
+
+
+Setting a Primary Locale ensures consistent fallback behavior across user sessions and browsers.
+
+
+### Previewing Locales in the Studio
+
+By default, no locale is selected when opening a page in the Studio.
+
+
+
+Developers can manually switch between configured locales during development to preview how translations appear across languages.
+
+
+
+
+ This is achieved by:
+ - Clicking the **Localization** button in the [Header panel](./pageLoaders/pageLoaderOverview.md#header-panel) of the page.
+
+ - Selecting any of the available locales to simulate the translated application view.
+
+
+
+
+
+
+This preview is strictly for **design-time visualization** within the canvas and does not affect runtime user sessions or language selection persistence.
+
+
+## Translating Content
+
+A **literal** is any visible text in the application interface (e.g., labels, button texts, titles). By default, each literal exists independently for each locale page.
+
+### Manual Translation
+
+Developers can manually modify the literal text **directly** in each locale's version without creating a translation key.
+
+
+
+
+
+
+
+
+
+
+:::info
+This method is simple for small applications but becomes harder to maintain as the number of locales grows.
+:::
+
+### Creating Translation Keys
+
+For better scalability, **literals can be assigned to translation keys**. A **translation key** links a text element to a centralized reference, allowing it to be easily managed and translated across all supported locales.
+
+Assigning a translation key enables centralized updates, reduces duplication, and ensures consistency when a literal is reused across different pages or components.
+
+To create a translation key:
+
+
+
+ 1. Double-click the text within the text component you want to translate.
+
+
+
+
+
+2. From the tooltip, click on the **i18n** button .
+
+
+ 3. A modal will appear showing the current text value.
+
+
+
+
+
+
+
+ 4. In the **Search for keys** field, type the desired key name (for example, `marketingText_Key`).
+
+
+
+
+
+
+
+ 5. The system will suggest creating the key.
+
+ 6. Click **Create** to link your text to the new key.
+
+
+
+
+
+
+Once created:
+
+- The translation key will be automatically associated with the text.
+
+
+ :::tip
+ The key will appear under all locales.
+ :::
+
+- Different values can be assigned for the same key depending on the active locale.
+
+### Translation Key Scenarios
+
+Here’s how key assignment works when localizing content:
+
+1. **Assigning a Key in the Base State (No Locale Selected)**: When no specific locale is selected (you’re in the base state), clicking the purple i18n button adds the key to all locales.
+
+2. **Assigning a Key While a Locale is Selected**: If a locale is selected when you click the i18n button, the key is still added to all locales, including the base state.
+
+3. **Manually Editing Labels per Locale (No Key Used)**: Instead of using keys, you can manually update the Label field for each locale.
+ - Select a locale and directly type the desired label.
+ - This is quick and flexible but may become harder to manage at scale.
+ - Use the approach that best matches the complexity and reuse patterns of your UI labels.
+
+
+### Removing a Key from a Literal
+
+If needed, you can remove a translation key association from a text component:
+
+
+
+ - Click again on the **i18n** button from the component’s tooltip.
+
+ - In the key field, click the **remove key** ("-") button next to the assigned key.
+
+
+
+
+
+
+This action will unlink the text from the translation key, allowing it to behave as a standard independent literal again.
+
+:::warning
+Removing a key from a literal in a specific locale will only affect that locale. The key remains available and associated in other locales where it has already been used.
+:::
+
+### Configuring Key Values Per Locale
+
+Translation keys maintain a **single unique identifier** across all locales. However, the **value** associated with the key is locale-specific, allowing developers to configure different translations for each supported language.
+
+To configure key values:
+
+- Navigate to the Localization page.
+- Select the desired locale.
+- Locate the relevant translation key.
+- Edit the literal value for that locale.
+- Confirm the changes.
+
+
+
+
+By switching between locales inside the Studio and previewing the pages, developers can immediately see how each configured translation appears for different languages.
+
+
+
+
+
+
+
+
+
+
+
+:::info Important Behavior for Key Creation
+
+When creating locales for the first time, **no keys exist initially**.
+
+- The first key must be created while translating elements directly on the page using the i18n buttton.
+- Once at least one key has been created through a component (e.g., a text field, button label), the Localization page becomes fully enabled for direct key management.
+- After the first key is created, you can create additional translation keys directly from the Localization page, even without selecting elements on the page.
+
+This ensures that key management is progressively unlocked as localization is set up.
+:::
+
+### Add i18n for Other Specific Components
+
+For components other than the Text component, adding i18n translation keys is done directly through the Properties panel in Qodly Studio.
+
+For example, to localize the label of a Button component:
+
+1. Select the component in your page.
+2. Go to the Properties tab in the right-hand panel.
+3. Locate the Label/Title field.
+4. You have three options:
+ - Assign a key to all locales: Click the purple i18n button next to the label field without selecting a locale. This adds the key for all supported locales.
+ - Assign a key to a specific locale: First select the target locale from the locale switcher, then click the i18n button. This will assign the key only for that selected locale.
+ - Manually edit label value: You can also manually change the value of the label per locale without using a translation key.
+
+
+## Managing Locales
+
+### Editing a Locale
+
+To edit an existing locale:
+
+- Navigate to the **Localization** page from the Explorer.
+- Locate the locale you want to modify in the list.
+- Click on the **Edit** button next to the locale name.
+- A language selector dropdown will appear.
+- Choose a new language to replace the current one.
+
+### Removing a Locale
+
+To completely remove a locale:
+
+- Navigate to the **Localization** page from the Explorer.
+- Find the locale you wish to remove.
+- Click on the **Delete** button next to the locale name.
+
+Removing a locale will:
+
+- Delete all translation keys associated with that locale.
+- Automatically update the `UserLanguage.supported` array, removing the deleted language from the runtime switching options.
+
+
+## Managing Keys
+
+### Editing a Translation Key
+
+To edit the value of a translation key for a specific locale:
+
+1. Go to the Localization page in Qodly Studio.
+2. Select the desired locale.
+3. Locate the key you wish to update from the list.
+4. Click the edit icon or directly click into the value field.
+5. Modify the value and click the ✔ button to confirm your changes or ✖ to cancel.
+
+
+
+### Deleting a Translation Key
+
+To delete a translation key entirely from the Localization page:
+
+1. Open the Localization page.
+2. Navigate to the locale where the key is visible.
+3. Locate the key in the list and click the delete icon.
+
+
+
+Deleting a key will:
+
+- Remove the key itself.
+- Remove all translations associated with it across all locales.
+- Unlink any components or literals that were referencing the deleted key.
+
+Use this option only when the key is no longer needed in the application.
+
+
+## Runtime Language Switching
+
+To enable dynamic, user-driven language switching at runtime, the application must be configured to bind to the [UserLanguage shared Qodly source](./pageLoaders/qodlySources.md#qodlysource-userlanguage).
+
+Runtime switching involves the following configuration steps:
+
+
+
+ 1. Bind the `UserLanguage.supported` to the listbox qodly source.
+
+ 2. Bind the `UserLanguage.selected` to the *Selected Element** of the listbox.
+
+
+
+
+
+
+This binding enables users to pick a language dynamically from a dropdown list during runtime.
+
+When the application is rendered:
+
+
+
+ - The select box allows the user to switch between available languages.
+
+
+
+
+
+
+- Upon selecting a language, the value of `UserLanguage.selected` is updated.
+
+- The application automatically refreshes the displayed text to match the translations configured for the selected locale.
+
+
+
+
+
+
+
+
+
+
+:::info
+If no UserLanguage binding is configured, language switching will not function in preview or renderer modes, even if locales exist.
+:::
+
+## Localization Feature Behavior
+
+Localization in Qodly follows a **layered priority model** to determine which language is displayed to the end user at any given moment.
+
+The priority order for language resolution is:
+
+1. **User-selected language**: If the user manually selects a language during their session (for example, using a language switcher dropdown), the application will immediately display the selected language and store it in the session.
+
+2. **Session-stored language**: On subsequent visits, if a session language has been previously stored, the application will automatically reuse this stored language preference without requiring user interaction.
+
+3. **Browser language detection**: If no session language is set (e.g., on a user's very first login), the application will attempt to detect the user's browser language.
+ - If the browser language matches one of the supported locales configured in the Localization page, it will be used automatically.
+
+4. **Primary locale fallback**: If neither a session language nor a matching browser language is available, the application will fall back to displaying the **Primary locale** defined in the Localization settings.
+
+:::info Behavior for Missing Translations
+
+If a literal does not have a translation value for the active locale, the original text value (literal) will be displayed as-is.
+
+This ensures that the application remains functional even if translations are incomplete.
+
+:::
+
+## Exporting and Importing Translations
+
+Qodly makes it easy to manage translations externally by allowing you to **export** and **import** translation files. You can export your application's literals to a CSV or JSON file, translate them externally, and then re-import them back into your application.
+
+### Export First, then Import
+
+Before you can import translations into Qodly, you must first export your application's literals. This ensures that the file structure matches the expected internal format.
+
+You cannot create a valid translation file from scratch or use a file exported from another app. The import process requires a file that was originally exported from your own Qodly app.
+
+Once exported, you can edit the file using a spreadsheet editor (for CSV) or a code/text editor (for JSON), then re-import it into your app.
+
+### Exporting Literals
+
+The **Export** button is initially disabled by default.
+
+To enable the Export button:
+
+1. You must select at least one locale from the Localization panel.
+
+2. You can either:
+
+ - Manually check the box for one or more locales.
+
+ - Or check the **Select All** checkbox to select all available locales at once.
+
+
+ Once at least one locale is selected, the **Export** button becomes active .
+
+3. Click **Export** to open the export configuration modal.
+
+
+
+ Inside the Export modal:
+ - Select your preferred export format:
+ - **CSV**: Suitable for spreadsheet editing or basic translation workflows.
+ - **JSON**: Suitable for structured editing and external system integration.
+ - After selecting the format, click **Export**.
+
+
+
+
+
+
+A file will be generated and downloaded in the chosen format containing all literals for the selected locales.
+
+### Importing Literals
+
+To import translated literals:
+ - Click the **Import** button located next to the Export options.
+
+
+ - A file selector will open.
+
+ - Upload a valid `.csv` or `.json` file matching the export format.
+
+ - Once a file is selected, click **Import** to update your application's literals.
+
+
+
+
+
+
+### Supported File Formats
+
+Qodly supports two export/import formats: JSON and CSV. Each format has its own structure and is suitable for different use cases.
+
+#### JSON Format
+
+The JSON format is structured for developers and tools that prefer a nested, key-based structure.
+
+| Field | Description |
+|-------|-------------|
+| `webforms.i18n` | Maps component IDs to their translation data. Each ID represents a UI element. |
+| `resolverName` | The type of component (e.g., Label, Text, SelectBox). |
+| `props` | An array of translated properties, each with: `__dataPath`, `__default`, and one or more language translations. |
+| `i18n` | Contains all custom translation keys created by the developer. Each key includes a `default` value and language-specific values. |
+
+**Example:**
+```json
+{
+ "webforms": {
+ "i18n": {
+ "1DVbAUpEoU": {
+ "resolverName": "Label",
+ "props": [
+ {
+ "__dataPath": "text",
+ "__default": "Label",
+ "en": "Label",
+ "es": "Label"
+ }
+ ]
+ },
+ ...
+ }
+ },
+ "i18n": {
+ "marketingText_Key": {
+ "default": "marketing Style Text",
+ "en": "Qodly Studio is a modern, low-code development environment...",
+ "fr": "Qodly Studio est un environnement de développement..."
+ }
+ }
+}
+```
+
+#### CSV Format
+
+The CSV format is ideal for translation teams working in spreadsheet editors.
+
+| Column | Description |
+|--------|-------------|
+| `keys` | Path to the literal or translation key. E.g., `webforms.i18n.Z7Gc1JzZ16.doc` or `i18n.marketingText_Key.en`. |
+| `resolverName` | Type of the UI component (`Label`, `Text`, etc.). |
+| `__t` | The default fallback value shown when a translation is missing. |
+| `en`, `es`, `fr`, etc. | Columns for each language code, containing the translated values. |
+| `key_default` | The value tied to a custom translation key when applicable. |
+
+**Example:**
+```
+keys,resolverName,__t,en,es,key_default,zh,ja,it,de,fr
+webforms.i18n.1DVbAUpEoU.text,Label,"Label","Label","Label"
+i18n.marketingText_Key.en,,,,,Qodly Studio is a modern, low-code development environment...
+```
diff --git a/versioned_docs/version-21/4DQodlyPro/notes.md b/versioned_docs/version-21/4DQodlyPro/notes.md
new file mode 100644
index 000000000..07e398f54
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/notes.md
@@ -0,0 +1,56 @@
+---
+id: release-notes
+title: Release Notes
+---
+
+
+
+## 4D 21
+
+### Highlights
+
+- [Localization (i18n)](./localization.md): Launched built-in Localization support, allowing you to create multilingual applications visually—without coding. You can define supported locales, manage translation keys and literals, preview translations directly in the Studio, and allow users to switch languages at runtime using the [UserLanguage](pageLoaders/qodlySources.md#qodlysource-userlanguage) shared source.
+
+- [Page Zoom Controls](pageLoaders/pageLoaderOverview.md#page-zoom-controls): in the header panel, allowing users to adjust the page’s zoom level for more precise component placement and layout editing.
+
+- [Events Report](pageLoaders/pageLoaderOverview.md#events-report): Introduced the Events Report, a visual overview of all page events for components and Qodly sources complete with filtering, editing, and navigation options.
+
+... more to come...
+
+
+
+## 4D 20 R10
+
+### Highlights
+
+- [Qodly Looker Studio Connector](../Integrations/qodlyLookerStudio/qodlyLookerStudioConnector.md): Added integration between Qodly applications and Google Looker Studio, enabling users to create interactive dashboards, track real-time business metrics, and generate custom reports using Qodly data.
+
+- [Saved Condition Go To Button](pageLoaders/states/conditionalState.md#saved-condition-integration): When a saved condition is integrated into a state, a **Go to** button now appears next to its name. Clicking it opens the full saved condition in edit mode—so you can quickly review or update it without leaving the schema view.
+
+- [Interval Range Validation for Text Input](pageLoaders/components/textinput.md#intervals-for-date-input): For text inputs using the **interval type `Range`**, if the **start date is later than the end date**, an error message will be shown and the dates will be temporarily disabled until corrected.
+
+- [Matrix Selection Behavior Options](pageLoaders/components/matrix.md#properties-customization): You can now control how the **Matrix** behaves after a data update (like reloading or filtering).
+
+- [Debugger Sidebar](./debugging.md#debugger-sidebar): A new sidebar in the code editor lets you monitor, group, enable/disable, delete, and jump to breakpoints across your entire app. It also shows a [Variables panel](./debugging.md#variables-panel) during debug sessions, so you can view local variables, current line variables, and method arguments—all in one place.
+
+- [Built-in Shared Qodly Namespace](pageLoaders/qodlySources.md#built-in-shared-qodly-namespace): Introduced a built-in Qodly namespace available across all application pages. It provides ready-to-use qodlysources for shared data handling, including a [Location shared qodlysource](pageLoaders/qodlySources.md#qodlysource-location) that simplifies working with URL segments, query parameters, and anchors. As well as, a [UserLanguage shared qodlysource](pageLoaders/qodlySources.md#qodlysource-userlanguage) that allows runtime management of user-selected languages and lists supported locales dynamically; and a [Title shared qodlysource](pageLoaders/qodlySources.md#qodlysource-title) that enables setting the browser tab title dynamically at runtime.
+
+- [Connection Status Handling in Renderer](rendering.md#connection-status-handling): The Renderer now displays connection status messages when the network is lost or restored during rendering. A red banner appears when disconnected, and a green banner confirms when the connection is restored.
+
+### Behavior Changes
+
+- **Renamed properties in the [intervals datasource](pageLoaders/components/textinput.md#params-object-properties)** of the text input component for consistency:
+
+ - `toDay` is now `today`
+ - `startingfrom` is now `startingFrom`
+ - `untilto` is now `until`
+
+- **Include Option and Interval Card Toggle**: Added visual controls to improve interval management. The [Include checkbox](pageLoaders/components/textinput.md#include-checkbox-within-the-card) lets users include or exclude specific date ranges, while the [card toggle](pageLoaders/components/textinput.md#card-toggle-top-right) allows enabling or disabling intervals without losing their configuration.
+
+- [HTTP Handlers UI Redesign](./httpHandlers.md): Updated the UI to provide a clearer and more intuitive layout, making it easier to configure and manage request handlers.
+
+- [Roles and Privileges UI – Button Label Update](./roles/permissionsOverview.md#clean-non-existing-resources): In the Roles and Privileges interface, the **Clear** button was renamed to **Clean** for clarity. This button appears when a resource (like an attribute) is no longer available, and lets users remove outdated permissions.
+
+- [Initial Value Editing in Qodly Source](pageLoaders/qodlySources.md#editing-a-qodly-source): For qodlysources with editable properties, initial values now include a **Maximize** button for fields of type `object` or `array`. Clicking the maximize icon opens a popup editor, which can also be expanded to fill the contextual panel, making it easier to edit complex or long values.
+
+- [Date Picker Navigation in Text Input](pageLoaders/components/textinput.md#embedded-input): Users can now navigate to the next/previous month and next/previous year directly within the date picker for a smoother selection experience.
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/button.md b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/button.md
new file mode 100644
index 000000000..49c68fc91
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/button.md
@@ -0,0 +1,143 @@
+---
+id: button
+title: Button
+---
+import Column from '@site/src/components/Column'
+
+
+The **Button** component is a UI element that prompts user engagement and triggers actions within your Page.
+
+
+:::info
+
+The **Button** component has an **Icon** element embedded within it. This is of great importance as configuring the **Button** component may require adjusting properties within the embedded element. This applies to the visual style, triggers, and actions as they may differ.
+
+:::
+
+
+## Use Cases
+
+Buttons are indispensable in a wide range of scenarios where user engagement and interaction are paramount:
+
+- **Form Submission**: Utilize buttons to allow users to submit forms after providing necessary input.
+
+- **Navigation**: Implement buttons to facilitate navigation between different sections or pages of your application.
+
+- **Data Manipulation**: Enable users to perform operations such as adding, editing, or deleting data entries through buttons.
+
+
+## Properties Customization
+
+### Button Component
+
+Enhance the **Button** component to align with your application's requirements using the following customization options:
+
+
+
+
+
Label: Personalize the label to offer clear instructions or guidance.
+
Icon Position: Choose the position of the icon in relation to the label, allowing options for top, bottom, left, right, or even hidden for a seamless integration into your design.
+
+
+
+
+
+
+
+
+### Embedded Icon
+
+Within the Button component, an embedded Icon allows for further customization of the following properties:
+
+
+
+
+
Icon: When the icon visibility is configured in the Button component's customization properties, you can select an icon from an icon picker list.
+
+
+
+
+
+
+
+## Data Integration
+
+When it comes to data-binding, it's important to note that the **Button** component itself is not inherently data-bound. Unlike components like the **DataTable** that derive their content from specified Qodly Sources, the **Button** component primarily focuses on initiating actions and interactions within the user interface.
+
+## Customizing Button Styles
+
+### Example 1 - Overall Component Style
+
+The `self` selector targets the entire Button component, allowing you to customize its size, shape, and appearance with background color and shadows.
+
+
+
+
+
+
+
+
+
+
+### Example 2 - Active Button Style
+
+The `self:active` selector applies when the button is actively pressed or clicked, providing visual feedback with a change in shadow effect to simulate a "pressed-in" appearance.
+
+
+
+
+
+
+
+
+
+
+
+## Triggers and Events
+
+### Button Component
+
+The **Button** component can respond to various events, enabling dynamic user experiences. Events that can trigger actions within the component include:
+
+|Event|Description|
+|---|---|
+|On Click| Calls for an action when the user clicks on the component. |
+|On Blur| Calls for an action when the component loses focus (user clicks outside). |
+|On Focus| Calls for an action when the component gains focus (user clicks on it). |
+|On MouseEnter| Calls for an action when the user's mouse cursor enters the area of the component.|
+|On MouseLeave| Calls for an action when the user's mouse cursor exits the area of the component.|
+|On Keyup| Calls for an action when a keyboard key is released while the component is in focus|
+|On KeyDown| Calls for an action when a keyboard key is pressed down while the component is in focus. |
+
+### Embedded Icon
+
+The embedded **Icon** can also respond to various events, allowing for dynamic user experiences. Events that can trigger actions within the embedded icon include:
+
+|Event|Description|
+|---|---|
+|On Click| Calls for an action when the user clicks on the Icon. |
+|On Keyup| Calls for an action when a keyboard key is released while the Icon is in focus|
+|On KeyDown| Calls for an action when a keyboard key is pressed down while the Icon is in focus. |
+|On MouseEnter| Calls for an action when the user's mouse cursor enters the area of the Icon.|
+|On MouseLeave| Calls for an action when the user's mouse cursor exits the area of the Icon.|
\ No newline at end of file
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/checkbox.md b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/checkbox.md
new file mode 100644
index 000000000..7592708da
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/checkbox.md
@@ -0,0 +1,290 @@
+---
+id: checkbox
+title: Checkbox
+---
+import Column from '@site/src/components/Column'
+
+The **Checkbox** component is a UI element designed for binary selections. It consists of a small box that can be checked (selected) or unchecked (deselected) by the user.
+
+:::info
+
+The **Checkbox** component contains an embedded **Checkbox Input** and a **Label** element. This is of great importance as configuring the **Checkbox** component may require adjusting properties within the embedded elements. This applies to the visual style, triggers, and actions as they may differ.
+
+:::
+
+
+## Use Cases
+
+The **Checkbox** component serve various purposes in user interfaces, including:
+
+- **Feature Control**: Enable/disable features.
+
+- **Preference Indication**: Reflect user preferences.
+
+- **Agree/Disagree Choices**: Obtain user consent.
+
+
+
+
+## Properties Customization
+
+### Checkbox Component
+
+Enhance the **Checkbox** component to align with your application's requirements using the following customization options:
+
+
+
+
+
Label Position: Developers can tailor the label's position, placing it above, below, to the left, to the right, or even hidden.
+
+
+
+
+
+
+
+
+- **Variant Selection**: Choose the variant that aligns with your design:
+
+
+
+
+
Checkbox Variant: Select this for the standard checkbox style.
+
Switch Variant: Choose this for a switch-like appearance.
+
+
+
+
+
+
+
+
+- **Checkbox Type selection**: Choose between three checkbox configurations to determine the available states and default values for the checkbox:
+
+
+
+
+
Two-State Checkbox (Default): Select this for straightforward binary decisions.
+
Three-State Checkbox: Select this for scenarios requiring an optional or undefined state.
+
Initial Indeterminate Checkbox: Select this to provide an indeterminate state initially, transitioning to two-state behavior after first use.
+
+
+
+
+
+
+
+ | **Type** | **States** | **Default Value** | **State Transition Order** |
+ |-------------------------|------------------------------------|---------------------------|------------------|
+ | **Two-State Checkbox** | - Checked (`true`): Option is selected. - Unchecked (`false`): Option is rejected. | `false` (unchecked) | - Checked - Unchecked |
+ | **Three-State Checkbox**| - Checked (`true`): Option is selected. - Unchecked (`false`): Option is rejected. - Indeterminate (`null`): No action has been taken or optional. | `null` (indeterminate) | - Indeterminate - Checked - Unchecked - Indeterminate |
+ | **Initial Indeterminate Checkbox** | - Checked (`true`): Option is selected. - Unchecked (`false`): Option is rejected. - Indeterminate (`null`): Only before first user action. | `null` (indeterminate) | - Indeterminate (before first action only) - Checked - Unchecked|
+
+
+
+
+
+
Size: Choose from small, medium, or large sizes for the Checkbox component.
+
+
+
+
+
+
+
+
+### Embedded Label
+
+Within the **Checkbox** component, an embedded **Label** allows for further customization of the following properties:
+
+
+
+
+
Label: Personalize the label to offer clear instructions or guidance.
+
+
+
+
+
+
+
+
+
+## Data Integration
+
+The **Checkbox** component allows for seamless integration of Qodly Sources, enabling dynamic data binding and interaction within the Page.
+
+:::info
+The qodlysource for the **Checkbox** component should be binary, with values limited to `true` or `false`.
+:::
+
+
+### Data Binding
+To associate data with the Checkbox component, follow these steps:
+
+1. **Navigate to the Properties Panel**: Access the Data Access category located within the Properties panel for the Checkbox component.
+
+2. **Define the Qodly Source**: Specify the relevant qodlysource that will capture the user's selected choice.
+
+:::tip
+Alternatively, you can establish the connection by dragging and dropping the qodlysource onto the Checkbox component.
+:::
+
+### Server-Side Interaction
+
+Interacting with user input data is straightforward. When you bind a qodlysource to the **Checkbox** component, you can access and make use of the input content.
+
+Subsequently, you can utilize this input value in various ways, such as within a standard action to initiate a search with matching attribute values.
+
+
+## Customizing CheckBox Styles
+
+The **Checkbox** component offers additional customization options through CSS, providing control over the appearance of checkbox elements.
+
+### Component-Specific Classes
+
+The following CSS classes are applied to various elements within the **CheckBox** component, defining their layout, style, and behavior. Each class can be customized to adjust the look and functionality of specific parts of the component.
+
+| **Class Name** | **Applies To** | **Description** |
+|------------------------------|--------------------------------------|----------------------------------------------------------------------------------------------------------------------------|
+| `chakra-checkbox` | CheckBox wrapper | Provides base styling for the CheckBox wrapper, including layout and positioning. |
+| `chakra-checkbox__input` | Checkbox input | Applies default styling for the actual checkbox input element, usually hidden to allow the control to display instead. |
+| `fd-label` | Label element | Styles the CheckBox label, including font size, color, and positioning relative to the checkbox input. |
+
+
+### Component-Specific Tags
+
+The following HTML tags make up the structure of the **CheckBox** component. Each tag can be styled to adjust its appearance, alignment, and user interaction.
+
+| **Tag Name** | **Applies To** | **Description** |
+|--------------|-----------------------------|----------------------------------------------------------------------------------------------------------------------------|
+| `` | Checkbox input field | The main checkbox where users select or deselect options. Styling the input affects borders, padding, and position, influencing how it appears alongside the label text. |
+
+
+### Example 1 - Overall CheckBox Style
+
+The `self .chakra-checkbox` selector targets the main CheckBox container, allowing customization of size, cursor behavior, and shadow effects.
+
+
+
+
+
+
+
+
+
+
+
+### Example 2 - Check Mark Control Style
+
+The `self .chakra-checkbox span` selector applies to the visual representation of the check mark control, adjusting its font size, weight, and transition effects.
+
+
+
+
+
+
+
+
+
+
+
+## Triggers and Events
+
+### Checkbox Component
+
+The **Checkbox** component can respond to various events, enabling dynamic user experiences. Events that can trigger actions within the component include:
+
+|Event|Description|
+|---|---|
+|On Click| Calls for an action when the user clicks on the component. |
+|On Blur| Calls for an action when the component loses focus (user clicks outside). |
+|On Focus| Calls for an action when the component gains focus (user clicks on it). |
+|On MouseEnter| Calls for an action when the user's mouse cursor enters the area of the component.|
+|On MouseLeave| Calls for an action when the user's mouse cursor exits the area of the component.|
+
+### Embedded Label
+
+The embedded **Label** can also respond to various events, allowing for dynamic user experiences. Events that can trigger actions within the embedded icon include:
+
+|Event|Description|
+|---|---|
+|On Click| Calls for an action when the user clicks on the Label. |
+|On Keyup| Calls for an action when a keyboard key is released while the Label is in focus|
+|On KeyDown| Calls for an action when a keyboard key is pressed down while the Label is in focus. |
+|On MouseEnter| Calls for an action when the user's mouse cursor enters the area of the Label.|
+|On MouseLeave| Calls for an action when the user's mouse cursor exits the area of the Label.|
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/componentsBasics.md b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/componentsBasics.md
new file mode 100644
index 000000000..ffb2d8747
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/componentsBasics.md
@@ -0,0 +1,207 @@
+---
+id: componentsBasics
+title: Components basics
+---
+import Column from '@site/src/components/Column'
+
+
+
+Components are the fundamental building blocks that constitute your application's user interface. These modular elements allow you to construct rich and dynamic interfaces by combining various functionalities and visual elements.
+
+
+
+## Locating Components
+
+
+
+ Components are located in the Components tab on the left side panel. Components are systematically organized into distinct categories, simplifying the search and selection process:
+
+
Containers: Components that encapsulate other components and manage their placement.
+
+
Simple: Components that display scalar or native data, images, and facilitate user actions
+
+
List: Components that iterate over qodlysources and manage user interactions with this data.
+
+
Custom Components: These are external React components that have been imported into Qodly by the user.
+
+
+
+
+
+
+
+## Adding Components
+
+Incorporating components into your Page is a straightforward process that involves **drag-and-drop** functionality. Simply select a component and place it onto your working area or canvas.
+
+You have the flexibility to drop components directly onto the Page itself, as well as within other components that accept nested elements. This versatility allows you to construct complex layouts and hierarchies by combining different components within one another.
+
+
+
+## Uploading Custom Components
+
+
+
+ Qodly Studio provides a dedicated upload button in the Component bar, allowing users to upload Custom Components.
+
+
+
+
+
+
+
+## Tooltip
+
+
+
+ When you select a component on the canvas, a contextual tooltip becomes available, offering a variety of actions that can be performed on the chosen component. These actions provide you with the tools to efficiently manage and manipulate your Page's design. Here's a breakdown of the actions available in the tooltip:
+
+
+
+
+
+
+
+
+### Essential Options
+
+|Option|Description|
+|---|---|
+|Move| Reposition the selected component on the canvas.|
+|Select Parent Component| Select the parent component of the current selection.|
+|Delete Component| Remove the selected component from the canvas. As an alternative solution, you can use the shortcut **Alt + Shift + Delete (⌥ + ⇧ + Delete for Mac)**.|
+|Export Styles| Export the overridden CSS properties to a new CSS class.|
+|Open Events Panel| Binding events to the selected component.|
+
+
+### More Options
+
+|Option|Description|
+|---|---|
+|Copy| Make a copy of the selected component. As an alternative solution, you can use the shortcut **Alt + C (⌥ + C for Mac)**|
+|Cut| Cuts the selected component so you can paste it elsewhere. As an alternative solution, you can use the shortcut **Alt + X (⌥ + X for Mac)**|
+|Paste| Paste the copied component directly into your Page. As an alternative solution, you can use the shortcut **Alt + V (⌥ + V for Mac)**|
+|Paste into| Paste the copied component into another component. As an alternative solution, you can use the shortcut **Alt + ⇧ + V (⌥ + ⇧ + V for Mac)**|
+|Duplicate| Duplicate the selected component in the same parent component. As an alternative solution, you can use the shortcut **Alt + D (⌥ + D for Mac)**|
+|Save as craft| Save the component, its child components, and their styles as a reusable component. As an alternative solution, you can use the shortcut **Alt + K (⌥ + K for Mac)**|
+|Copy content| Copy content. As an alternative solution, you can use the shortcut **Alt + ⇧ + C (⌥ + ⇧ + C for Mac)**|
+|Clear styles| Clear the styles of the selected component (resets the overridden CSS properties). As an alternative solution, you can use the shortcut **Alt + J (⌥ + J for Mac)**|
+|Clear content| Clear content within the selected component. As an alternative solution, you can use the shortcut **Alt + E (⌥ + E for Mac)**|
+
+
+## Data Formatting
+
+
+
+ Your Pages can incorporate Qodly Source attributes with data types such as string, number, date, time, or duration. When these attributes are presented within components, you have the option to choose their display format in the Properties section.
+
+
+
+
+
+
+:::info
+Formatting options may vary based on the specific data type of the attribute and the type of component being used.
+:::
+
+
+
+
+
For Text (string):
+
+
UPPERCASE: Converts all characters to uppercase.
+
lowercase: Converts all characters to lowercase.
+
Capitalize: Capitalizes the first letter of the text.
+
Capitalize Each Word: Capitalizes the first letter of each word.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
For Number:
+
+
0: Standard numerical format.
+
0%: Displays the number as a percentage.
+
#,##0: Adds thousands separators to the number.
+
#,##0.00: Adds thousands separators and displays two decimal places.
+
+
+ Refer to the Review guidelines for customizing number formats.
+
+
+
+
+
+
+
+
+
+
+
+
+
For Date:
+
+
Date short: Displays the date in a short format.
+
Date long: Displays the date in a long format.
+
Date abbreviated: Displays the date with abbreviated month name.
+
ISO date GMT: Displays the date in ISO 8601 format with GMT time zone.
+
UTC String: Displays the date in a UTC format.
+
+
+ Qodly supports a wide range of customized date formats. For a comprehensive list of available patterns, refer to the Date and Time Formats page.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
For Duration:
+
+
Simple: 14:40:30
+
Without seconds: 14:40
+
Distance: about 15 hours
+
Distance with Suffix: in about 15 hours
+
Strict Distance: 15 hours
+
Strict Distance with Suffix: in 15 hours
+
+ Note: Examples with duration 52 830 000 ms.
+
+
+ Qodly supports a wide range of customized time formats. For a comprehensive list of available time patterns, refer to the Date and Time Formats page.
+
+
+
+
+
+
+
+## Data Integration
+
+This section serves as a fundamental introduction to the integration of Qodly Sources with components. It's essential to note that each component has its own dedicated section on its respective page, including the following subsections:
+
+- **Data Binding**: This step configures the connection between a component and a Qodly Source, determining where the component retrieves its data.
+
+- **Data Display**: After binding data, this section guides you in visually presenting data within the component, including configuring its appearance and additional components.
+
+- **Dynamic Attribute Display**: This section explains how a component can automatically display attributes of a selected Qodly Source, offering detailed information without extra interactions.
+
+- **Server-Side Interaction**: This section explains obtaining user selections in a server-side context when a Qodly Source is bound to a component, enabling content retrieval and use.
+
+:::note
+Please note that the following subsections (Data Binding, Data Display, Dynamic Attribute Display, and Server-Side Interaction) may or may not be applicable to all components. Their presence or absence depends on the specific component's functionality and use case.
+:::
\ No newline at end of file
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/datatable.md b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/datatable.md
new file mode 100644
index 000000000..955a8f02f
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/datatable.md
@@ -0,0 +1,382 @@
+---
+id: datatable
+title: Data Table
+---
+import Column from '@site/src/components/Column'
+
+The **DataTable** component is a versatile UI element used to display data in a tabular format, resembling a table. It efficiently iterates through a designated Qodly Source, converting it into an organized list comprising rows and columns. In the DataTable, columns represent attributes, rows represent entities, and a header row labels the columns.
+
+
+
+## Use Cases
+
+The **DataTable** component proves invaluable across a multitude of scenarios where data needs to be comprehensively displayed, including:
+
+- **Data Analysis**: Utilize the DataTable to showcase datasets, enabling users to perform in-depth analysis, exploration, and comparison of various attributes.
+
+- **Inventory Management**: Employ the DataTable in inventory management systems to present product details, quantities, and availability.
+
+- **Sales Reports**: For tracking sales performance, the DataTable effectively displays revenue, product sales, and other relevant metrics.
+
+
+
+## Properties Customization
+
+Enhance the **DataTable** component to align with your application's requirements using the following customization options:
+
+
+
+
+
Header Height: Define the height of the header row in pixels.
+
+
+
+
+
+
+
+
+
+
+
Row Height: Set the height of each row (excluding the header) in pixels.
+
+
+
+
+
+
+
+
+
+
+
Show Borders: Enable or disable the display of line and column borders within the DataTable.
+
+
+
+
+
+
+
+
+
+
+
+
Sorting Behavior: Control the scrolling behavior of the DataTable after sorting columns. This feature improves navigation and focus in large tables by offering two options:
+
+
Scroll to Selection (default): After sorting, the table keeps the selected element in focus and ensures it remains in view.
+
Scroll to Top: After sorting, the table scrolls to the top while retaining the selected element in the table.
+
+
+
+
+
+
+
+
+- **Columns Area**: The columns area is where developers can manage the columns within the DataTable. It provides options for adding, moving, or removing columns to customize the structure of the table.
+
+ - **Adding Columns**: To add a new column, click on the `+` icon. This action triggers the appearance of a new column configuration area located at the bottom of the column list. This area allows you to define properties for the new column:
+
+
+
+
Title: The title is the text displayed in the header row as the label for the column. It also appears as the column name in the properties area.
+
Source: The source attribute specifies the qodlysource for the column. Typically, it refers to an attribute whose value depends on each element of the DataTable's qodlysource. This determines the content to be displayed in the column cells.
+
Format: The format property allows you to define how the data in the column should be displayed, depending on its type. It specifies the visual representation of the data, such as date formatting or decimal places. See Formats for a description of available formats.
+
Width: The width of the column can be customized. You have the option to define the width in pixels or as a percentage. The unit menu at the right side of the entry area lets you choose between PX (pixels) or % (percentage).
+
Sorting: The sorting selector enables users to interactively sort the column. When this selector is activated, users can click on the header area of the column to perform ascending or descending sorting at runtime.
+
+
+
+
+
+
+
+ - **Column Duplication**: Duplicate existing columns to replicate configurations quickly by clicking on the icon.
+ - **Column Removal**: Delete columns that no longer serve a purpose by clicking on the icon.
+
+ - **Moving Column**: Arrange columns to your preferred position by clicking on the icon
+
+## Data Integration
+
+The **DataTable** component is data-bound, meaning it relies on an external Qodly Source to populate its options. This allows the component to display and interact with data.
+
+:::info
+The qodlysource for the **DataTable** component can take the form of either an `ORDA entity selection` or an `array`.
+:::
+
+### Data Binding
+
+To associate data with the **DataTable** component, follow these steps:
+
+
+
+
+
Navigate to the Properties Panel: Access the Data Access category located within the Properties panel for the Data Table component.
+
Define the Qodly Source: Specify the appropriate qodlysource that contains the data you want to display within the DataTable. For instance, you can select an entity selection, such as the Packages dataclass.
+
+
+
+
+
+
+
+:::tip
+Alternatively, you can establish the connection by dragging and dropping the qodlysource onto the DataTable component.
+:::
+
+### Data Display
+
+When it comes to displaying columns in the DataTable component, you have two options:
+
+- **Property Customization in the Columns Area**: Modify column settings according to your preferences directly from the [Columns Area](#properties-customization).
+
+- **Attribute Drag-and-Drop**: Alternatively, you can include columns by dragging and dropping attributes from the Qodly Source onto the DataTable.
+
+### Dynamic Attribute Display
+
+
+
+
+ The Data Table component can link its currently selected entity to a qodlysource in the Selected Element field. This feature allows the component to automatically display the attributes of the selected element whenever a new entity is chosen.
+
+
+
+
+
+
+
+
+ These attributes can be showcased in other configured components, such as a Text component, to display the corresponding attribute values.
+
+
+
+
+
+
+### Server-Side Interaction
+
+Enhance DataTable interactivity by binding functions to events like `onheaderclick` and `oncellclick`. These events respond to user actions and retrieve event-specific details using the [webEvent](../../../QodlyinCloud/qodlyScript/commands/webEvent) command.
+
+Common attributes for `onheaderclick` and `oncellclick`:
+
+|Attribute|Type|Description|
+|---|---|---|
+|caller| Text | Server-side reference of the DataTable component.|
+|data| Object | The data object containing event-specific information.|
+|eventType| Text | Type of event that has been triggered ("onheaderclick" or "oncellclick")|
+
+- **onheaderclick**: Triggered when a user clicks a column header. The data object attribute includes:
+
+|Attribute|Type|Description|
+|---|---|---|
+|index| Number | The index of the clicked column header (starting from 0).|
+|name| Text | The qodlysource of the column.|
+
+
+- **oncellclick**: Triggered when a user clicks a cell in a row. The data object attribute includes:
+
+|Attribute|Type|Description|
+|---|---|---|
+|index| Number | The index of the clicked column header (starting from 0).|
+|name| Text | The qodlysource of the column.|
+|row| Number | The number of the clicked row.|
+
+
+## User Experience Features
+
+The **DataTable** component provides a range of user-friendly features:
+
+- **Column Sorting**: Users can sort data by clicking column headers .
+
+- **Resizable Columns**: Adjust column widths to fit content .
+
+- **Drag-and-Drop Column Movement**: Rearrange columns with ease .
+
+- **Selectable/Tabbable Rows**: Navigate and select rows using keyboard tabbing.
+
+
+
+## Customizing DataTable Styles
+
+The **DataTable** component offers extensive customization options through CSS, enabling adjustments to the appearance of various table elements.
+
+
+
+### Component-Specific CSS Classes
+
+The DataTable component supports a range of CSS classes, allowing customization of the appearance of headers, rows, cells, and columns. Below is a detailed list of supported CSS classes and the elements they apply to.
+
+#### Table Header Classes
+
+| **Class Name** | **Applies To** | **Description** |
+|------------------|-------------------------------|--------------------------------------------|
+| `.header` | The entire header | Styles the entire table header area. |
+| `.header-cell` | All header cells | Targets individual header cells. |
+| `.header-even` | Even-numbered header cells | Applies styles to header cells in even columns. |
+| `.header-qodlysourceName` | The header of a specific column | Targets the header of a specific column, where `qodlysourceName` refers to the associated data field (e.g., `.header-lastname`). |
+
+#### Table Row Classes
+
+| **Class Name** | **Applies To** | **Description** |
+|------------------|-------------------------------|--------------------------------------------|
+| `.row` | All rows | Applies styles to all rows in the table. |
+| `.selected` | The selected row | Highlights the currently selected row. |
+| `.row-even` | Even-numbered rows | Styles the even rows in the table. |
+| `.hover` | The hovered row | Styles the row that is currently hovered. |
+
+#### Table Cell Classes
+
+| **Class Name** | **Applies To** | **Description** |
+|------------------|-------------------------------|--------------------------------------------|
+| `.cell` | All cells | Targets all cells within the rows. |
+
+#### Table Column Classes
+
+| **Class Name** | **Applies To** | **Description** |
+|------------------|-------------------------------|--------------------------------------------|
+| `.col-qodlysourceName` | A specific column | Targets the column associated with a particular data field (e.g., `.col-lastname`). |
+| `.col-even` | Even-numbered columns | Styles columns that are even-numbered. |
+| `.sorted` | Sorted columns | Highlights columns that are sorted. |
+| `.sorted-asc` | Ascending sorted columns | Applies styles to columns sorted in ascending order. |
+| `.sorted-desc` | Descending sorted columns | Applies styles to columns sorted in descending order. |
+
+:::tip
+For fields associated with relationships, use a dash (`-`) instead of a dot (`.`) in class names (e.g., `.header-employer-name`).
+:::
+
+### Example 1 – Blue Headers
+
+This example customizes the DataTable to feature blue-colored headers with rounded borders, and shadow effects. Additionally, a soft blue background is applied to the header cells, and a light shade is used for even-numbered rows.
+
+
+
+
+```css
+self {
+ border-radius: 10px;
+ box-shadow: 0 0 40px 0 rgb(0 0 0 / 15%);
+}
+
+self .header .header-cell {
+ background-color: #6c7ae0;
+ color: #fff;
+ font-weight: bold;
+ padding: 1.25rem 2rem;
+}
+
+self .row {
+ align-items: center;
+}
+
+self .row .cell {
+ padding: 0.5rem 2rem;
+}
+
+self .row.selected {
+ background-color: #d2d7f5;
+}
+
+self .row-even:not(.selected):not(:hover) {
+ background-color: #f8f6ff;
+}
+```
+
+### Example 2 – Green Headers
+
+This example customizes the DataTable to feature a green color scheme, with specific styles for even-numbered header cells.
+
+
+
+```css
+self .header .header-cell {
+ background-color: #324960;
+ color: #fff;
+ font-weight: bold;
+ font-size: 14px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+self .header .header-even {
+ background-color: #4fc3a1;
+}
+
+self .row {
+ align-items: center;
+}
+
+self .row.selected {
+ background-color: #caede2;
+}
+```
+
+This example highlights alternating header cells with a green background and provides custom styling for selected rows.
+
+
+### Example 3 – Dark Mode DataTable
+
+This example demonstrates a dark mode design for the DataTable, where the background and text colors are adjusted for better visibility in low-light environments.
+
+
+
+```css
+self {
+ background-color: rgb(61, 54, 61);
+}
+
+self .row {
+ align-items: center;
+}
+
+self .header .header-cell {
+ background-color: transparent;
+ color: #fff;
+ font-size: 11px;
+ text-transform: uppercase;
+}
+
+self .row:hover .col-lastname {
+ color: #b474e4;
+ font-weight: bold;
+}
+```
+
+This dark theme provides a sleek and modern look, with specific styles applied to hovered rows and the `lastname` column.
+
+
+## Showcase
+
+Here's a glimpse of how the **DataTable** component will look and behave in action:
+
+
+
+:::info
+Customize the styles of the DataTable component by utilizing specific CSS classes that target various elements of the DataTable. For more details, refer to the section on [Customizing DataTable Styles](#customizing-datatable-styles).
+:::
+
+## Triggers and Events
+
+The **DataTable** component can respond to various events, enabling dynamic user experiences.
+
+Additional information including the **column number**, **row number**, and **column qodlysource name** are returned by the [`webEvent` command](../../../QodlyinCloud/qodlyScript/commands/webEvent.md) when called in an event function triggered by a **DataTable** component.
+
+Events that can trigger actions within the component include:
+
+|Event|Description|
+|---|---|
+|On Select| Calls for an action when an item within the component is selected. |
+|On Click| Calls for an action when the user clicks on the component. |
+|On DoubleClick| Calls for an action when the user double-clicks on the component. |
+|On HeaderClick| Calls for an action when the user clicks on the header of a column. |
+|On HeaderDoubleClick| Calls for an action when the user double-clicks on the header of a column. |
+|On CellClick| Calls for an action when the user clicks on a cell within the component. |
+|On CellDoubleClick| Calls for an action when the user double-clicks on a cell within the component. |
+|On Keyup| Calls for an action when a keyboard key is released while the component is in focus|
+|On KeyDown| Calls for an action when a keyboard key is pressed down while the component is in focus. |
+|On CellMouseEnter| Calls for an action when the user's mouse cursor enters a cell within the component.|
+|On MouseEnter| Calls for an action when the user's mouse cursor enters the area of the component.|
+|On MouseLeave| Calls for an action when the user's mouse cursor exits the area of the component.|
+
+
+
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/dialog.md b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/dialog.md
new file mode 100644
index 000000000..25c63379e
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/dialog.md
@@ -0,0 +1,215 @@
+---
+id: dialog
+title: Dialog
+---
+
+import Column from '@site/src/components/Column'
+
+
+
+A Dialog is an interactive, dynamic UI element designed as a popup overlay to streamline user engagement by rendering additional content or forms within the existing page context.
+
+
+## Dialog Creation
+
+To initiate a dialog, navigate to the edited Page header panel and select the `Dialogs` button .
+
+
+
+ Initially, it will indicate There are no configured Dialogs yet. However, once dialogs are created, they will be listed under this section.
+
+ To create a new dialog, click on the New dialog button within this panel.
+
+
+
+
+
+
+
+
+
+ This will trigger a modal where you can assign a name to the dialog.
+
+
+
+
+
+
+
+
+After naming and confirming with the create button, the new dialog will appear both in the dialog's list and within the outline view.
+
+
+
+
+
+
+
+
+
+
+
+:::info
+At runtime, dialogs are displayed at the center of the page.
+:::
+
+
+## Dialog Deletion
+
+There are two ways for removing Dialogs:
+
+
+
+ - Dialogs List: Click on the delete icon next to the dialog you intend to delete in the dialogs listt.
+
+
+
+
+
+
+
+
+
+ - Outline section: As an alternative method, dialogs can also be removed directly from the outline section..
+
+
+
+
+
+
+
+:::info
+
+
+
+ Regardless of the chosen method, a confirmation step is required in a follow-up modal to finalize the deletion process.
+
+
+
+
+
+
+:::
+
+
+## Dialog Renaming
+
+
+
+ To change the name of a dialog, select the edit icon next to the desired dialog in the dialogs list. This action activates the edit mode within the same interface, allowing you to rename the dialog.
+
+
+
+
+
+
+
+## Dialog Content Editing
+
+
+
+ To edit a dialog, select it from the dialog list in the header panel:
+
+
+
+
+
+
+
+
+ Or, access it via the outline:
+
+
+
+
+
+
+This will open the dialog within the current Page, providing a dedicated interface for modifying its content.
+
+:::info
+
+
+
+ The dialog that is currently being edited in the Page will be highlighted in the dialog list, making it easy to identify which dialog is under modification.
+
+
+
+
+
+
+:::
+
+
+Each dialog includes a style box that enables the integration and customization of various components. Components can be dragged and dropped directly into this style box, allowing for efficient customization of the dialog's appearance.
+
+
+
+
+:::tip
+To exit the dialog's editing mode, click the close button located at the top right corner.
+:::
+
+
+## Properties Customization
+
+Enhance the Dialog component to align with your application's requirements using the following customization options:
+
+
+
+
Overlay (Boolean): Activates a screen overlay when set to true.
+
Draggable (Boolean): Enables user-driven repositioning. Features a customizable move icon .
+
Closable (Boolean): Integrates a close function, operable via the button or by clicking outside the dialog (if overlay is enabled). Features a customizable close icon .
+
Animated: Implements an opening flicker effect for visual emphasis.
+
+
+
+
+
+
+
+
+## Server-Side Interaction
+
+Every dialog is uniquely named, which acts as its server-side reference for server-side interactions. This allows you to control the Dialog's behavior, such as [hiding](../../../QodlyinCloud/qodlyScript/WebFormItemClass.md#hide), [showing](../../../QodlyinCloud/qodlyScript/WebFormItemClass.md#show), [adding CSS classes](../../../QodlyinCloud/qodlyScript/WebFormItemClass.md#addcssclass), or [removing CSS classes](../../../QodlyinCloud/qodlyScript/WebFormItemClass.md#removecssclass) from it.
+
+:::tip
+Employing the `.show()` method triggers the `On Open` event, whereas utilizing `.hide()` triggers the `On Close` event.
+:::
+
+
+
+## Display and Customization
+
+The overlay style of dialogs can be further styled using the shared CSS class `fd-dialog-overlay`, such as:
+
+```css
+fd-dialog-overlay {
+ background-color: rgba(137, 43, 226, 50%);
+}
+```
+
+:::info
+This shared CSS class takes precedence and cannot be superseded by any locally defined classes of the same name.
+:::
+
+
+## Showcase
+
+Here's a glimpse of how the **Dialog** component will look and behave in action:
+
+
+
+
+## Triggers and Events
+
+The Dialog component can respond to various events, enabling dynamic user experiences. Events that can trigger actions within the component include:
+
+| Event | Description |
+|-----------|--------------------------------------------------------------|
+| On Init | Calls for an action when the dialog initially opens. It is used for the dialog's initialization. |
+| On Loaded | Calls for an action once the dialog has completely mounted/loaded, indicating readiness for interaction. |
+| On Close | Calls for an action to be performed just before the dialog is closed. |
+
+:::tip
+In scenarios where the `Dialog` includes a `Page Loader`, these events are triggered before the `On Load` actions of the `Page Loader`.
+:::
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/fileupload.md b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/fileupload.md
new file mode 100644
index 000000000..83ccc4b32
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/fileupload.md
@@ -0,0 +1,164 @@
+---
+id: fileupload
+title: File Upload
+---
+import Column from '@site/src/components/Column'
+
+
+The **File Upload** component is an interactive UI element that simplifies file transfers by allowing users to easily upload files to the server.
+
+
+:::info
+
+The **File Upload** component has an **Icon** element embedded within it. This is of great importance as configuring the **File Upload** component may require adjusting properties within the embedded element. This applies to the visual style, triggers, and actions as they may differ.
+
+:::
+
+
+## Use Cases
+
+The **File Upload** component finds valuable application in diverse scenarios, including:
+
+- **Document Management**: Facilitate efficient document uploads and storage for improved organization.
+
+- **Media Sharing**: Allow users to upload images, videos, or audio files for seamless sharing with others.
+
+- **File Archiving**: Provide users with a means to securely store and retrieve essential files or records.
+
+
+
+## Properties Customization
+
+### File Upload Component
+
+Enhance the **File Upload** component to align with your application's requirements using the following customization options:
+
+
+
+
+
Label: Personalize the label to offer clear instructions or guidance.
+
+
+
+
+
+
+
+
+
+
+
Icon Position: Choose the position of the icon in relation to the label, allowing options for top, bottom, left, right, or even hidden.
+
+
+
+
+
+
+
+
+
+
+
Size Limit: Define the maximum file size users are allowed to upload. Choose from units such as KB, MB, and GB.
+ If a user attempts to upload a file exceeding the specified size, an error message is displayed in the browser.
+
+
+
+
+
+
+
+
+
+
+
+
Media Type: Specify the supported file types that can be uploaded using the component. Supported types include text, image, video, and audio.
+
+
+
+
+
+
+
+
+### Embedded Icon
+
+Within the **File Upload** component, an embedded **Icon** allows for further customization of the following properties:
+
+
+
+
+
Icon: When the icon visibility is configured in the File Upload component's customization properties, you can select an icon from an icon picker list.
+
+
+
+
+
+
+
+## Data Integration
+
+The **File Upload** component uses data-binding to link user interactions with the underlying data structure.
+
+:::info
+The qodlysource for the **File Upload** component can take the form of either a `Blob` or a `Picture`.
+:::
+
+### Data Binding
+
+
+
+ Bind the component to an attribute of Blob or Picture type within an entity qodlysource. This specific attribute will serve as the storage location for the uploaded file.
+
+
+
+
+
+
+:::tip
+Alternatively, you can establish the connection by dragging and dropping the qodlysource onto the File Upload component.
+:::
+
+## Example: Sending the File and File Name
+
+To send both a file and its name to the database, the File Upload component must be bound to a Picture field (e.g., imageEntity.Content) within an entity. However, to capture the file name along with the file itself, you must also reference the entity in the action that handles the file upload.
+
+For example, in the "Send" button of your form, you can use a function with two parameters:
+
+uploadImage(imageEntity, imageEntity.Content)
+
+To implement this, your function should retrieve the picture from imageEntity.Content and the file name from the object (e.g., OB Get($oImage; "path")), then save both to a new entity instance.
+
+## Showcase
+
+Here's a glimpse of how the **File Upload** component will look and behave in action:
+
+
+
+
+## Triggers and Events
+
+### File Upload Component
+
+The **File Upload** component can respond to various events, enabling dynamic user experiences. Events that can trigger actions within the component include:
+
+|Event|Description|
+|---|---|
+|On Click| Calls for an action when the user clicks on the component. |
+|On Blur| Calls for an action when the component loses focus (user clicks outside). |
+|On Focus| Calls for an action when the component gains focus (user clicks on it). |
+|On MouseEnter| Calls for an action when the user's mouse cursor enters the area of the component.|
+|On MouseLeave| Calls for an action when the user's mouse cursor exits the area of the component.|
+|On KeyDown| Calls for an action when a keyboard key is pressed down while the component is in focus. |
+|On Keyup| Calls for an action when a keyboard key is released while the component is in focus|
+
+### Embedded Icon
+
+The embedded **Icon** can also respond to various events, allowing for dynamic user experiences. Events that can trigger actions within the embedded icon include:
+
+|Event|Description|
+|---|---|
+|On Click| Calls for an action when the user clicks on the Icon. |
+|On Keyup| Calls for an action when a keyboard key is released while the Icon is in focus|
+|On KeyDown| Calls for an action when a keyboard key is pressed down while the Icon is in focus. |
+|On MouseEnter| Calls for an action when the user's mouse cursor enters the area of the Icon.|
+|On MouseLeave| Calls for an action when the user's mouse cursor exits the area of the Icon.|
\ No newline at end of file
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/icon.md b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/icon.md
new file mode 100644
index 000000000..cc16aaab0
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/icon.md
@@ -0,0 +1,52 @@
+---
+id: icon
+title: Icon
+---
+import Column from '@site/src/components/Column'
+
+The **Icon** component is a UI element that displays graphical symbols or icons in web applications.
+
+
+## Use Cases
+
+The **Icon** component serves various purposes within user interfaces, including:
+
+- **Action Indicators**: Icons are used to represent actions, such as "Save," "Delete," or "Print".
+
+- **Navigation Enhancements**: Icons can be employed to enhance navigation by representing menu items, links, or categories.
+
+- **Information Presentation**: Icons are utilized to visually convey information, such as alerts, warnings, or success messages.
+
+
+## Properties Customization
+
+Enhance the **Icon** component to align with your application's requirements using the following customization options:
+
+
+
+
+
Icon: Select the desired icon from a predefined list of icons.
+
+
+
+
+
+
+
+
+## Data Integration
+
+When it comes to data-binding, it's important to note that the **Icon** component itself is not inherently data-bound. Unlike components like the **DataTable** that derive their content from specified qodlysources, the main purpose of the **Icon** component is to provide a visual representation.
+
+
+## Triggers and Events
+
+The **Icon** component can respond to various events, enabling dynamic user experiences. Events that can trigger actions within the component include:
+
+|Event|Description|
+|---|---|
+|On Click| Calls for an action when the user clicks on the component. |
+|On Keyup| Calls for an action when a keyboard key is released while the component is in focus|
+|On KeyDown| Calls for an action when a keyboard key is pressed down while the component is in focus. |
+|On MouseEnter| Calls for an action when the user's mouse cursor enters the area of the component.|
+|On MouseLeave| Calls for an action when the user's mouse cursor exits the area of the component.|
\ No newline at end of file
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/image.md b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/image.md
new file mode 100644
index 000000000..05ccfba23
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/image.md
@@ -0,0 +1,97 @@
+---
+id: image
+title: Image
+---
+import Column from '@site/src/components/Column'
+
+
+The **Image** component is a UI element that enables you to integrate images into your Page.
+
+## Use Cases
+
+The **Image** component offers versatile solutions for enhancing your web application's visual experience:
+
+- **Static Images**: Display fixed visual elements, such as logos, icons, and decorative images.
+- **Dynamic Content**: Display dynamic content fetched from qodlysources.
+- **Data Visualization**: Display charts, graphs, and diagrams as images, enabling the visualization of complex data in a more accessible and engaging manner.
+
+
+## Display Options
+
+The **Image** component offers multiple options for displaying images.
+
+### Qodly Source Binding
+
+
+
+ You can bind the Image component to a qodlysource of type image, causing the displayed image to update according to the value within the qodlysource.
+
+
+
+
+
+
+
+### Image Source
+
+
+
+ Alternatively, you can specify an image source directly. This source can be a URL or a path to the image stored in the Shared folder.
+
+
+
+
+
+
+:::info
+For instance, if you've uploaded an image named "booking.png" in a "visuals" folder, you can set the image source as `/$shared/visuals/booking.png`.
+:::
+
+
+## Image Management
+
+### Direct Image Upload
+
+The Image component simplifies the process of adding new images. By clicking on the image component and uploading the desired picture, the image is automatically added to the Shared folder's /$shared/assets/images subdirectory. The component's image source is updated accordingly.
+
+
+:::tip
+Alternatively, you can establish the connection by dragging and dropping an image qodlysource onto the Page, which will automatically add an Image component with the qodlysource bound to it.
+:::
+
+
+### Default Image Source
+
+
+
+
+ Qodly displays a "missing image" placeholder in the Image component when the bound image qodlysource is null, such as when the "photo" attribute of an "Employee" entity has not been uploaded:
+
+
+
+
+
+
+
+
+
+ Use the Default Image Source property to define your own default image, allowing you to replace the standard placeholder with a custom image that fits your application's aesthetics or branding:
+
+
+
+
+
+
+### Drag and Drop from Shared Folder
+
+The Image component supports a drag and drop functionality that allows users to move images directly from a shared folder to become the image source. This feature is particularly useful for rapidly updating the image souce without the need for navigating through files.
+
+## Triggers and Events
+
+The **Image** component can respond to various events, enabling dynamic user experiences. Events that can trigger actions within the component include:
+
+|Event|Description|
+|---|---|
+|On Click| Calls for an action when the user clicks on the component. |
+|On MouseEnter| Calls for an action when the user's mouse cursor enters the area of the component. |
+|On MouseLeave| Calls for an action when the user's mouse cursor exits the area of the component. |
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/BlueHeaders.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/BlueHeaders.png
new file mode 100644
index 000000000..50cca574a
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/BlueHeaders.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/Dark-mode.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/Dark-mode.png
new file mode 100644
index 000000000..0b17c960b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/Dark-mode.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/GreenHeaders.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/GreenHeaders.png
new file mode 100644
index 000000000..f46246b2a
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/GreenHeaders.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/Matrix_EmbeddedStylebox.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/Matrix_EmbeddedStylebox.png
new file mode 100644
index 000000000..79a0e961a
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/Matrix_EmbeddedStylebox.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/SelectBox_EmbeddedStylebox.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/SelectBox_EmbeddedStylebox.png
new file mode 100644
index 000000000..20ed70c98
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/SelectBox_EmbeddedStylebox.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/Tabs_EmbeddedStylebox.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/Tabs_EmbeddedStylebox.png
new file mode 100644
index 000000000..f7cbb6c05
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/Tabs_EmbeddedStylebox.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/addingComponents.gif b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/addingComponents.gif
new file mode 100644
index 000000000..7fe4668b7
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/addingComponents.gif differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/arrange.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/arrange.png
new file mode 100644
index 000000000..dc8025493
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/arrange.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/button_PropertiesCustomization.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/button_PropertiesCustomization.png
new file mode 100644
index 000000000..c357bc7c4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/button_PropertiesCustomization.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/button_icon.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/button_icon.png
new file mode 100644
index 000000000..159a65ec4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/button_icon.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/button_style1.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/button_style1.png
new file mode 100644
index 000000000..427c398a3
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/button_style1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/button_style2.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/button_style2.png
new file mode 100644
index 000000000..9d4ae81e6
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/button_style2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox_Size.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox_Size.png
new file mode 100644
index 000000000..7714c325e
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox_Size.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox_TypeSelection.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox_TypeSelection.png
new file mode 100644
index 000000000..5e5453855
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox_TypeSelection.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox_VariantSelection.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox_VariantSelection.png
new file mode 100644
index 000000000..d1c88ce35
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox_VariantSelection.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox__style1.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox__style1.png
new file mode 100644
index 000000000..75dcb039e
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox__style1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox__style2.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox__style2.png
new file mode 100644
index 000000000..c066887b6
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox__style2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox__style3.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox__style3.png
new file mode 100644
index 000000000..a3f7b0a8f
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox__style3.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox__style4.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox__style4.png
new file mode 100644
index 000000000..31589bc88
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/checkbox__style4.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/currrentElem.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/currrentElem.png
new file mode 100644
index 000000000..36edbd4f4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/currrentElem.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_1.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_1.png
new file mode 100644
index 000000000..d326e25e5
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_10.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_10.png
new file mode 100644
index 000000000..12cd42527
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_10.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_11.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_11.png
new file mode 100644
index 000000000..6be4295c5
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_11.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_2.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_2.png
new file mode 100644
index 000000000..68010b4e0
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_3.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_3.png
new file mode 100644
index 000000000..ce96ac66f
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_3.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_4.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_4.png
new file mode 100644
index 000000000..e4dc56750
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_4.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_5.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_5.png
new file mode 100644
index 000000000..62f81af6c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_5.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_6.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_6.png
new file mode 100644
index 000000000..ab2e2493f
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_6.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_7.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_7.png
new file mode 100644
index 000000000..d7dcaa5d7
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_7.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_8.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_8.png
new file mode 100644
index 000000000..0ed1b49b5
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_8.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_9.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_9.png
new file mode 100644
index 000000000..edff78f73
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/customComponent_9.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_ColumnsArea.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_ColumnsArea.png
new file mode 100644
index 000000000..fd8e88621
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_ColumnsArea.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_DataBinding.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_DataBinding.png
new file mode 100644
index 000000000..8bb820183
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_DataBinding.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_HeaderHeight.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_HeaderHeight.png
new file mode 100644
index 000000000..c88f47ff6
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_HeaderHeight.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_Preview.gif b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_Preview.gif
new file mode 100644
index 000000000..03cafbb70
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_Preview.gif differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_RowHeight.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_RowHeight.png
new file mode 100644
index 000000000..dc033735b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_RowHeight.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_ShowBorders.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_ShowBorders.png
new file mode 100644
index 000000000..0ca482839
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_ShowBorders.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_SortingBehavior.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_SortingBehavior.png
new file mode 100644
index 000000000..92e3adde0
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_SortingBehavior.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_rearrange.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_rearrange.png
new file mode 100644
index 000000000..6aadab6d8
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_rearrange.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_resize.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_resize.png
new file mode 100644
index 000000000..3d5711c3d
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_resize.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_sort.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_sort.png
new file mode 100644
index 000000000..49760bd18
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dataTable_sort.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/datatable-schema.jpg b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/datatable-schema.jpg
new file mode 100644
index 000000000..6c26ccad5
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/datatable-schema.jpg differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/delete.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/delete.png
new file mode 100644
index 000000000..3cda0ea90
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/delete.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogClosable.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogClosable.png
new file mode 100644
index 000000000..a3c4ea81c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogClosable.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogCreationModal.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogCreationModal.png
new file mode 100644
index 000000000..f71884582
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogCreationModal.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDelete1.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDelete1.png
new file mode 100644
index 000000000..ca265ccbb
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDelete1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDelete2.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDelete2.png
new file mode 100644
index 000000000..0b9cbf94b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDelete2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDelete3.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDelete3.png
new file mode 100644
index 000000000..9c902934f
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDelete3.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDelete4.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDelete4.png
new file mode 100644
index 000000000..4eafda88e
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDelete4.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDraggable.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDraggable.png
new file mode 100644
index 000000000..8bd098d61
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogDraggable.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEdit1.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEdit1.png
new file mode 100644
index 000000000..c508e97af
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEdit1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEdit2.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEdit2.png
new file mode 100644
index 000000000..8041eccb7
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEdit2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEdit3.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEdit3.png
new file mode 100644
index 000000000..89fba18de
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEdit3.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEdit4.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEdit4.png
new file mode 100644
index 000000000..099906e71
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEdit4.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEditOutline.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEditOutline.png
new file mode 100644
index 000000000..7724fc36d
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogEditOutline.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogProperties.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogProperties.png
new file mode 100644
index 000000000..0a138ec4a
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogProperties.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogRename1.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogRename1.png
new file mode 100644
index 000000000..b9a0b69e5
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogRename1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogRename2.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogRename2.png
new file mode 100644
index 000000000..a51f2e7fe
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogRename2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialog_Preview.gif b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialog_Preview.gif
new file mode 100644
index 000000000..96a14e063
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialog_Preview.gif differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogs.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogs.png
new file mode 100644
index 000000000..4d3291c14
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogs.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogsList.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogsList.png
new file mode 100644
index 000000000..5a115123d
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogsList.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogsOutline.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogsOutline.png
new file mode 100644
index 000000000..8e67eb478
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dialogsOutline.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/duplicate.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/duplicate.png
new file mode 100644
index 000000000..b089dd029
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/duplicate.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dynamicAttributeDisplay_SelectedElement.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dynamicAttributeDisplay_SelectedElement.png
new file mode 100644
index 000000000..66a74a4c8
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dynamicAttributeDisplay_SelectedElement.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dynamicAttributeDisplay_attributeValue.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dynamicAttributeDisplay_attributeValue.png
new file mode 100644
index 000000000..042b79763
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/dynamicAttributeDisplay_attributeValue.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_IconPosition.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_IconPosition.png
new file mode 100644
index 000000000..6e60482bd
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_IconPosition.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_Label.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_Label.png
new file mode 100644
index 000000000..a8f01e3d3
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_Label.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_MediaType.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_MediaType.png
new file mode 100644
index 000000000..9dfa583f4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_MediaType.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_Preview.gif b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_Preview.gif
new file mode 100644
index 000000000..8fb1441b4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_Preview.gif differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_SizLimitError.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_SizLimitError.png
new file mode 100644
index 000000000..2c67a4260
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_SizLimitError.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_SizeLimit.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_SizeLimit.png
new file mode 100644
index 000000000..498562436
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_SizeLimit.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_dataBinding.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_dataBinding.png
new file mode 100644
index 000000000..7332518f5
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/fileUpload_dataBinding.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/format_duration.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/format_duration.png
new file mode 100644
index 000000000..53e9a4c0b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/format_duration.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/formats.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/formats.png
new file mode 100644
index 000000000..12b091f4c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/formats.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/image_Default.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/image_Default.png
new file mode 100644
index 000000000..b734be910
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/image_Default.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/image_Default_factory.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/image_Default_factory.png
new file mode 100644
index 000000000..79983bea6
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/image_Default_factory.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/image_ImageSource.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/image_ImageSource.png
new file mode 100644
index 000000000..346d88a1e
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/image_ImageSource.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/image_QodlySourceBinding.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/image_QodlySourceBinding.png
new file mode 100644
index 000000000..2939eaff1
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/image_QodlySourceBinding.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/includeDateRange.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/includeDateRange.png
new file mode 100644
index 000000000..c109cdf48
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/includeDateRange.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_date.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_date.png
new file mode 100644
index 000000000..ad11d30c5
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_date.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_dateIntervals1.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_dateIntervals1.png
new file mode 100644
index 000000000..482ddecb4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_dateIntervals1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_dateIntervals2.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_dateIntervals2.png
new file mode 100644
index 000000000..a3e1a49a8
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_dateIntervals2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_dateWeekStarts.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_dateWeekStarts.png
new file mode 100644
index 000000000..db7f0d74a
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_dateWeekStarts.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_duration.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_duration.png
new file mode 100644
index 000000000..42a3d04b2
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_duration.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_number.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_number.png
new file mode 100644
index 000000000..b8bc2a543
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_number.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_password.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_password.png
new file mode 100644
index 000000000..8f0ab4cb4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_password.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_passwordIconPosition.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_passwordIconPosition.png
new file mode 100644
index 000000000..4d2ad4f4d
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_passwordIconPosition.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_passwordTooltip.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_passwordTooltip.png
new file mode 100644
index 000000000..7f19e7df5
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_passwordTooltip.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_passwordTooltip2.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_passwordTooltip2.png
new file mode 100644
index 000000000..9ed7485c0
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_passwordTooltip2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_passwordTooltip3.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_passwordTooltip3.png
new file mode 100644
index 000000000..cc838c413
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_passwordTooltip3.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_text.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_text.png
new file mode 100644
index 000000000..a086b1168
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_text.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_textArea.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_textArea.png
new file mode 100644
index 000000000..676df3476
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_textArea.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_time.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_time.png
new file mode 100644
index 000000000..e00d30ee1
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/inputType_time.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/intervalCardToggle.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/intervalCardToggle.png
new file mode 100644
index 000000000..23a992476
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/intervalCardToggle.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/locatingComponents.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/locatingComponents.png
new file mode 100644
index 000000000..0cf39efc5
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/locatingComponents.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrixCustomizableScrollPositioning.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrixCustomizableScrollPositioning.png
new file mode 100644
index 000000000..800442edd
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrixCustomizableScrollPositioning.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_DataBinding.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_DataBinding.png
new file mode 100644
index 000000000..f037fdd19
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_DataBinding.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_DataDisplay.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_DataDisplay.png
new file mode 100644
index 000000000..854fc65eb
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_DataDisplay.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_DataDisplay_embeddedMatrix.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_DataDisplay_embeddedMatrix.png
new file mode 100644
index 000000000..9200cba2c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_DataDisplay_embeddedMatrix.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_Preview.gif b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_Preview.gif
new file mode 100644
index 000000000..849848c9c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_Preview.gif differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_ServerSideInteraction_RelatedEntity.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_ServerSideInteraction_RelatedEntity.png
new file mode 100644
index 000000000..f5ef9be49
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_ServerSideInteraction_RelatedEntity.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_fdVirtualGrid.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_fdVirtualGrid.png
new file mode 100644
index 000000000..77fa949e6
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_fdVirtualGrid.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_hover.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_hover.png
new file mode 100644
index 000000000..261a1d45e
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_hover.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_innerScrollContainer.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_innerScrollContainer.png
new file mode 100644
index 000000000..7a90b290b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_innerScrollContainer.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_orientations.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_orientations.png
new file mode 100644
index 000000000..221d4cc11
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_orientations.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_schema.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_schema.png
new file mode 100644
index 000000000..d79b965d1
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_schema.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_selected.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_selected.png
new file mode 100644
index 000000000..5f0cc7715
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/matrix_selected.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/noConfiguredDialogs.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/noConfiguredDialogs.png
new file mode 100644
index 000000000..c35c6a8d1
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/noConfiguredDialogs.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_DirectConfiguration.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_DirectConfiguration.png
new file mode 100644
index 000000000..d4bd43870
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_DirectConfiguration.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_DynamicBinding.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_DynamicBinding.png
new file mode 100644
index 000000000..b177b6ebf
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_DynamicBinding.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_InitialValue.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_InitialValue.png
new file mode 100644
index 000000000..544ec91cc
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_InitialValue.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_Navigation.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_Navigation.png
new file mode 100644
index 000000000..cd943b116
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_Navigation.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_Navigation_Preview.gif b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_Navigation_Preview.gif
new file mode 100644
index 000000000..c375859e1
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/pageloader_Navigation_Preview.gif differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_DataBinding.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_DataBinding.png
new file mode 100644
index 000000000..f262a813e
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_DataBinding.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_Options.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_Options.png
new file mode 100644
index 000000000..f864ea923
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_Options.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_Preview.gif b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_Preview.gif
new file mode 100644
index 000000000..e02cbee50
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_Preview.gif differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_defaultValue.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_defaultValue.png
new file mode 100644
index 000000000..6affb2332
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_defaultValue.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_fd-radio__item.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_fd-radio__item.png
new file mode 100644
index 000000000..b61a9acf8
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_fd-radio__item.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_fd-radio__item_hovered.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_fd-radio__item_hovered.png
new file mode 100644
index 000000000..27d90eb18
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_fd-radio__item_hovered.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_fd-radio__item_selected.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_fd-radio__item_selected.png
new file mode 100644
index 000000000..7d7c24c19
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_fd-radio__item_selected.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_mode.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_mode.png
new file mode 100644
index 000000000..6347e88f4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_mode.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_standardAction.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_standardAction.png
new file mode 100644
index 000000000..360ec470f
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_standardAction.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_type.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_type.png
new file mode 100644
index 000000000..a4804609b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/radio_type.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_DataDisplay.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_DataDisplay.png
new file mode 100644
index 000000000..148ebb488
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_DataDisplay.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_LabelPosition.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_LabelPosition.png
new file mode 100644
index 000000000..c5788149f
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_LabelPosition.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_orientation.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_orientation.png
new file mode 100644
index 000000000..19aab3597
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_orientation.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_sliderProperties.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_sliderProperties.png
new file mode 100644
index 000000000..ff5c44ea6
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_sliderProperties.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style1.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style1.png
new file mode 100644
index 000000000..98c3edf52
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style2.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style2.png
new file mode 100644
index 000000000..ccbbee447
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style3.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style3.png
new file mode 100644
index 000000000..1f28094b4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style3.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style4.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style4.png
new file mode 100644
index 000000000..1f8125b74
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style4.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style5.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style5.png
new file mode 100644
index 000000000..f183051a1
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style5.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style6.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style6.png
new file mode 100644
index 000000000..b20ea6467
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeInput_style6.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeIntervalInconsistentDates.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeIntervalInconsistentDates.png
new file mode 100644
index 000000000..8d16deaf3
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/rangeIntervalInconsistentDates.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_DataBinding.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_DataBinding.png
new file mode 100644
index 000000000..4448a6913
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_DataBinding.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_DataDisplay.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_DataDisplay.png
new file mode 100644
index 000000000..9a5810bb9
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_DataDisplay.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_EnableSearch.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_EnableSearch.png
new file mode 100644
index 000000000..66de81691
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_EnableSearch.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_NumberOfItems.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_NumberOfItems.png
new file mode 100644
index 000000000..df4c70518
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_NumberOfItems.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_Placeholder.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_Placeholder.png
new file mode 100644
index 000000000..9763891ce
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_Placeholder.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_Preview.gif b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_Preview.gif
new file mode 100644
index 000000000..7baf6f02f
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_Preview.gif differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_ServerSideInteraction_RelatedEntity.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_ServerSideInteraction_RelatedEntity.png
new file mode 100644
index 000000000..624f6da12
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_ServerSideInteraction_RelatedEntity.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_ShowLength.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_ShowLength.png
new file mode 100644
index 000000000..9e13b16f4
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectBox_ShowLength.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_DataBinding.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_DataBinding.png
new file mode 100644
index 000000000..48564e821
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_DataBinding.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_Options.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_Options.png
new file mode 100644
index 000000000..19c7107f7
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_Options.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_Preview.gif b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_Preview.gif
new file mode 100644
index 000000000..def3168fe
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_Preview.gif differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_option.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_option.png
new file mode 100644
index 000000000..50645a922
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_option.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_option_checked.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_option_checked.png
new file mode 100644
index 000000000..80059dc44
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_option_checked.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_select.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_select.png
new file mode 100644
index 000000000..9cd8b9aad
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_select.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_standardAction.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_standardAction.png
new file mode 100644
index 000000000..221db7880
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectInput_standardAction.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_container.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_container.png
new file mode 100644
index 000000000..340b8256c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_container.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_container_hover.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_container_hover.png
new file mode 100644
index 000000000..9ac9460d2
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_container_hover.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_fd_virtual_list.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_fd_virtual_list.png
new file mode 100644
index 000000000..551e2fad9
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_fd_virtual_list.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_menu_wrapper.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_menu_wrapper.png
new file mode 100644
index 000000000..c1bbd6411
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_menu_wrapper.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_wrapper.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_wrapper.png
new file mode 100644
index 000000000..8565bbf36
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/selectbox_wrapper.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/styleBox_dataBinding_entityAttribute.gif b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/styleBox_dataBinding_entityAttribute.gif
new file mode 100644
index 000000000..f01d46f69
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/styleBox_dataBinding_entityAttribute.gif differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/styleBox_dataBinding_length.gif b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/styleBox_dataBinding_length.gif
new file mode 100644
index 000000000..8f35744d3
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/styleBox_dataBinding_length.gif differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tab_CanvasAddition.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tab_CanvasAddition.png
new file mode 100644
index 000000000..47238fc25
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tab_CanvasAddition.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tabs_PlusButtonAddition.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tabs_PlusButtonAddition.png
new file mode 100644
index 000000000..dadf19141
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tabs_PlusButtonAddition.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tabs_Preview.gif b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tabs_Preview.gif
new file mode 100644
index 000000000..243a427c8
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tabs_Preview.gif differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tabs_VariantSelection.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tabs_VariantSelection.png
new file mode 100644
index 000000000..cfe98cb13
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tabs_VariantSelection.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_DataBinding.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_DataBinding.png
new file mode 100644
index 000000000..6a9a6d641
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_DataBinding.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_DataDisplay.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_DataDisplay.png
new file mode 100644
index 000000000..56f3f51ef
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_DataDisplay.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputCustomization.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputCustomization.png
new file mode 100644
index 000000000..96dc6d118
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputCustomization.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFomat_Date.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFomat_Date.png
new file mode 100644
index 000000000..357b7082f
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFomat_Date.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFomat_Number.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFomat_Number.png
new file mode 100644
index 000000000..576909295
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFomat_Number.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFomat_Text.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFomat_Text.png
new file mode 100644
index 000000000..c9f6a0078
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFomat_Text.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFomat_Time.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFomat_Time.png
new file mode 100644
index 000000000..bc97263b3
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFomat_Time.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFormat.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFormat.png
new file mode 100644
index 000000000..d477a4709
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_InputFormat.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_LabelPosition.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_LabelPosition.png
new file mode 100644
index 000000000..c04414803
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_LabelPosition.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_Server-SideInteraction.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_Server-SideInteraction.png
new file mode 100644
index 000000000..9c3593a71
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_Server-SideInteraction.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_label.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_label.png
new file mode 100644
index 000000000..49e8cab33
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_label.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_readonly.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_readonly.png
new file mode 100644
index 000000000..9fd3878b5
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_readonly.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_standardAction.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_standardAction.png
new file mode 100644
index 000000000..ca114f976
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_standardAction.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_style1.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_style1.png
new file mode 100644
index 000000000..9e7f9d8bd
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_style1.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_style2.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_style2.png
new file mode 100644
index 000000000..6132cf6c2
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_style2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_style3.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_style3.png
new file mode 100644
index 000000000..7675455ee
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_style3.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_style4.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_style4.png
new file mode 100644
index 000000000..c3addf415
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/textInput_style4.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_Display_Flex.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_Display_Flex.png
new file mode 100644
index 000000000..edd7ff1eb
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_Display_Flex.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_FormatSupport.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_FormatSupport.png
new file mode 100644
index 000000000..52b1cccb8
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_FormatSupport.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_PropertiesCustomization.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_PropertiesCustomization.png
new file mode 100644
index 000000000..6e8ba65bd
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_PropertiesCustomization.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_StyleButtons.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_StyleButtons.png
new file mode 100644
index 000000000..65322538b
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_StyleButtons.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_ToggleLink.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_ToggleLink.png
new file mode 100644
index 000000000..9d90bf471
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_ToggleLink.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_ToggleqodlySource.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_ToggleqodlySource.png
new file mode 100644
index 000000000..dff1f0cbb
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/text_ToggleqodlySource.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/thumb-display.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/thumb-display.png
new file mode 100644
index 000000000..02b5c4018
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/thumb-display.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/thumb.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/thumb.png
new file mode 100644
index 000000000..1448dafc5
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/thumb.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tooltip2.png b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tooltip2.png
new file mode 100644
index 000000000..5d618df5c
Binary files /dev/null and b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/img/tooltip2.png differ
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/matrix.md b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/matrix.md
new file mode 100644
index 000000000..ebc788687
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/matrix.md
@@ -0,0 +1,266 @@
+---
+id: matrix
+title: Matrix
+---
+import Column from '@site/src/components/Column'
+
+
+The **Matrix** component is a UI element designed to display a dynamic array of Stylebox components.
+
+:::info
+
+Categorized under iterative components, the **Matrix** component is specialized in showcasing dynamic arrays of **Stylebox** components based on the component's designated qodlysource.
+
+:::
+
+
+Upon being placed on the canvas, the Matrix component includes a single Stylebox component, and this Stylebox is duplicated for each iteration of data.
+
+
+## Use Cases
+
+The **Matrix** component finds application in various scenarios where data needs to be displayed iteratively and can be customized. Common use cases include:
+
+- **Product Gallery**: Create an interactive product gallery where each Stylebox represents a product with its image, name, and price.
+
+- **News Feed**: Design a dynamic news feed by using the Matrix component to iterate over news articles. Each Stylebox can display the headline and a brief excerpt.
+
+- **User Profile Showcase**: Build a user profile showcase where the Matrix iterates over user profiles, displaying profile images and usernames.
+
+
+## Properties Customization
+
+Enhance the **Matrix** component to align with your application's requirements using the following customization options:
+
+- **Orientation**: Developers can choose between horizontal and vertical orientations for the Matrix. This choice affects the arrangement of Stylebox components and the presence of scrollbars.
+
+
+
+
+
Vertical Orientation (Default): By default, the Matrix component is oriented vertically. This means that Stylebox components are stacked from top to bottom. If the content surpasses the defined height, a vertical scrollbar will automatically be shown, facilitating vertical scrolling to access all the content.
+
Horizontal Orientation: When the Matrix component is configured with a horizontal orientation, Stylebox components are arranged from left to right. If the content exceeds the available width, a horizontal scrollbar will automatically appear, enabling users to scroll through the content horizontally.
+
+
+
+
+
+
+
+- **Selection Behavior**: Defines how the Matrix component behaves after data is updated, such as after reloading or applying a filter. Developers can choose from different behaviors to control how the Matrix scrolls and whether an element is automatically selected.
+
+
+
+
+
Select & Scroll First: With this option, the Matrix automatically scrolls to the top and selects the first element whenever data is reloaded or filtered.
+
Select & Scroll Current (Default): By default, the Matrix scrolls to the currently selected item and keeps it in view after a data update.
+
No Select: With this option, the Matrix retains its current scroll position and does not auto-select any element after a data update. Use this option when you want full control over selection. It's ideal when you're manually setting the selected item—for example, by calling a function or using a standard action that updates the selected element datasource yourself.
+
+
+
+
+
+
+
+:::info
+The Selection Behavior option is only visible if the currently selected element in the Matrix is filled.
+:::
+
+## Data Integration
+
+The **Matrix** component is data-bound, meaning it relies on an external qodlysource to populate its options. This allows the component to display and interact with data.
+
+:::info
+The qodlysource for the **Matrix** component can take the form of either an `ORDA entity selection` or an `array`.
+:::
+
+### Data Binding
+
+To associate data with the **Matrix** component, follow these steps:
+
+
+
+
+
Navigate to the Properties Panel: Access the Data Access category located within the Properties panel for the Matrix component.
+
Define the Qodly Source: Specify the appropriate qodlysource that contains the data you want to display within the Matrix. For instance, you can select an entity selection from a relevant dataclass, such as roomSelection.
+
+
+
+
+
+
+
+:::tip
+Alternatively, you can establish the connection by dragging and dropping the qodlysource onto the Matrix component.
+:::
+
+### Data Display
+
+To display data iterated over a Qodly Source, you can follow these additional steps:
+
+1. **Access the Matrix**: Within the Matrix component, locate the embedded Stylebox.
+2. **Add a Component**: Add a Text component or other relevant components within the Stylebox.
+
+
+3. **Configure the Component**: Click on the component you've added to enter its editing mode. The process of configuring components varies based on their type:
+
+ - **Toggle Qodly Source**: Prepare to connect the component to the qodlysource in the next step by using the `Toggle Qodly Source` button.
+
+ - **Properties Panel**: In the next step, you'll configure them through the `Data Access` category in the Properties panel.
+
+4. **Choose the Iterator**: Choose the iterator corresponding to the iterated data (e.g., $This) to represent the current data item.
+
+
+5. **Choose the Attribute**: Once you've selected the iterator, choose the specific attribute that you want to display within the component. This could include related entity selections. For example, use an embedded Matrix within the same Matrix to present various choices like room options (with/without breakfast).
+
+
+
+### Dynamic Attribute Display
+
+
+
+
+ The Matrix component can link its currently selected entity to a qodlysource in the Selected Element field. This feature allows the component to automatically display the attributes of the selected element whenever a new entity is chosen.
+
+
+
+
+
+
+
+
+ These attributes can be showcased in other configured components, such as a Text component, to display the corresponding attribute values.
+
+
+
+
+
+
+### Server-Side Interaction
+
+You can associate functions with embedded components in the **Matrix** component using `$This`. This capability enables the execution of functions from the entity class of the qodlysource that is providing data and being iterated upon in response to event triggers, such as button clicks.
+
+
+To implement this functionality, follow these steps:
+
+
+
+
+
Integrate the Matrix component into the interface.
+
Select a Qodly Source like Rooms for the Matrix.
+
Embed a component (e.g., a button) within the Matrix for each iterated data.
+
Bind the desired function, like selectRoomOption, to the component's event, such as a button click, using $This.
+
In the code editor, within the function, you can directly retrieve the data of the currently selected element without the need to pass the selected element qodlysource as a parameter to the function.
+
+
+
+
+
+
+
+
+:::tip
+The same principle applies to Related Entity Interaction when you have embedded Matrices within the primary Matrix to manage each iterated data. You can link the intended function to the embedded Matrix's component event using `$This`.
+:::
+
+## Customizing Matrix Styles
+
+The **Matrix** component offers additional customization options through CSS, allowing the personalization of the appearance of matrix elements.
+
+
+### Understanding Matrix CSS Classes
+
+The Matrix component supports a range of CSS classes, enabling customization of the appearance of the inner scroll container, the virtual grid and the default styleboxes. Below is a detailed list of supported CSS classes and the elements they apply to.
+
+#### Matrix Classes
+
+| **Class Name** | **Applies To** | **Description** |
+|------------------|-------------------------------|--------------------------------------------|
+| `.FdVirtualGrid` | The entire matrix | Styles the entire matrix area. |
+| `.innerScrollContainer` | All the default styleboxes in the matrix | Targets all the [default styleboxes](stylebox.md#enhancing-content-presentation) incorporated in the matrix. |
+| `.innerScrollContainer > div.selected` | The selected stylebox | Applies styles to the currently selected stylebox. |
+| `.innerScrollContainer > div:hover` | The hovered stylebox | Applies styles to the currently hovered stylebox. |
+| `.fd-stylebox > div` | The internal elements of styleboxes | Targets the internal components of the styleboxes that form the main Matrix. |
+
+:::tip
+It is important to prefix each class name with **self** to ensure it is applied specifically to the matrix it is associated with, rather than being applied to all matrices with the same class name.
+:::
+
+### Custom styling examples
+The following examples demonstrate how to customize the Matrix component's appearance.
+
+#### Example 1 - Virtual Grid
+This example styles the matrix with a white background, rounded corners, a double border, and shadow effects for added depth. The text color is changed to black.
+
+
+
+```css
+self .FdVirtualGrid {
+ background-color: white;
+ border-radius: 10px;
+ border-style: double;
+ border-color: rgb(187, 196, 243);
+ border-width: 1px;
+ box-shadow: -4px 4px 8px rgba(187, 196, 243, 0.5), -4px 4px 10px rgba(187, 196, 243,0.5);
+ padding: 20px;
+ color: rgb(21, 18, 18);
+}
+```
+#### Example 2 - Inner Scroll Container
+This example customize the inner scroll container with a light shade of white, rounded borders, a double border style, and shadow effects . This styling differenciates the `innerScrollContainer` from the `FdVirtualGrid` class.
+
+
+
+```css
+self .innerScrollContainer {
+ background-color: rgb(254, 253, 255);
+ box-shadow: -4px 4px 8px rgba(187, 196, 243, 0.86), -4px 4px 20px rgba(187, 196, 243, 0.874);
+ border-radius: 10px;
+ border-color: rgb(187, 196, 243);
+}
+```
+#### Example 3 - Selected Stylebox
+In this example, the selected stylebox is customized with a periwinkle blue background, blue text color, and italic font style.
+
+
+
+```css
+self .innerScrollContainer > div.selected {
+ font-family: italic;
+ background-color: rgb(187, 196, 243);
+ color:rgb(19, 19, 197);
+}
+```
+#### Example 4 - Hovered Stylebox
+This example customizes the hovered stylebox in the matrix with a rounded border, cursive font, green text color, and shadow effects for a highlighted appearance. Additionally, the border color is set to a light green.
+
+
+
+```css
+self .innerScrollContainer > div:hover {
+ box-shadow: -4px 4px 8px rgba(187, 196, 243, 0.4), -6px 6px 20px rgba(187, 196, 243, 0.4);
+ font-family:cursive;
+ border-radius: 10px;
+ border-color: rgb(198, 222, 200);
+ color:rgb(27, 88, 8);
+}
+```
+
+## Showcase
+
+Here's a glimpse of how the **Matrix** component will look and behave in action:
+
+
+
+
+## Triggers and Events
+
+The **Matrix** component can respond to various events, enabling dynamic user experiences. Events that can trigger actions within the component include:
+
+|Event|Description|
+|---|---|
+|On Click| Calls for an action when the user clicks on the component. |
+|On Keyup| Calls for an action when a keyboard key is released while the component is in focus|
+|On KeyDown| Calls for an action when a keyboard key is pressed down while the component is in focus. |
+|On MouseEnter| Calls for an action when the user's mouse cursor enters the area of the component.|
+|On MouseLeave| Calls for an action when the user's mouse cursor exits the area of the component.|
+|On Select| Calls for an action when an item within the component is selected. |
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/pageLoader.md b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/pageLoader.md
new file mode 100644
index 000000000..8c8faf16f
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/pageLoader.md
@@ -0,0 +1,77 @@
+---
+id: pageLoader
+title: Page Loader
+---
+import Column from '@site/src/components/Column'
+
+The **Page Loader** component is a versatile UI element designed to integrate one Page into another within the context of your application.
+
+
+## Use Cases
+
+The **Page Loader** component offers a multitude of applications, enhancing the modularity and interactivity of your Pages:
+
+- **Nested Pages**: Nest one Page within another to facilitate complex UI structures and content hierarchies.
+
+- **Dynamic Content**: Utilize the Page Loader to display dynamic content that can be updated on-the-fly through data binding
+
+- **Conditional Loading**: Implement conditional loading of Pages based on user interactions or specific conditions.
+
+
+## Properties Customization
+
+
+
+ The Page Loader component functions by displaying a designated Page within the current Page. The Page to be displayed is determined based on the initial value set for the Page Loader component. This initial value can be configured using either of the following methods:
+
+
+
+
+
+
+
+
+
+
+ - Direct Configuration: Utilize the Properties panel of the Page Loader to directly choose the target Page from a dropdown list.
+
+
+
+
+
+
+
+
+ - Dynamic Binding: Establish a binding with a qodlysource of type Text. By binding the component to this qodlysource and assigning it an initial value, you gain the flexibility to dynamically update the contents of the Page Loader. The initial value should correspond to the name of the target Page you intend to display.
+
+
+
+
+
+
+
+Note that:
+:::info
+In the case of concurrent application of both methods, the content sourced from the bound qodlysource takes priority.
+:::
+
+## Navigating with Page Loader
+
+When orchestrating navigation actions through the Page Loader component, it's crucial to employ the `Ref` property to specify the components requiring processing during the navigation event. This property guarantees that the target Page is loaded, and its contents are integrated into the current Page.
+
+
+
+Consider a travel agency's backoffice solution, where a content manager needs to manage various aspects of travel packages. For instance, when the content manager attempts to delete a specific flight associated with a package, clicking on the delete button can trigger a navigation action. This action could lead to the loading of the Page `modal_removeFlight` using the **Page Loader** component with the reference `0FW_eM-2g1`.
+
+
+
+## Triggers and Events
+
+The **Page Loader** component can respond to various events, enabling dynamic user experiences. Events that can trigger actions within the component include:
+
+|Event|Description|
+|---|---|
+|On Click| Calls for an action when the user clicks on the component. |
+|On DblClick| Calls for an action when the user double-clicks on the component. |
+|On MouseEnter| Calls for an action when the user's mouse cursor enters the area of the component.|
+|On MouseLeave| Calls for an action when the user's mouse cursor exits the area of the component.|
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/radio.md b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/radio.md
new file mode 100644
index 000000000..5dd1fa9b7
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/radio.md
@@ -0,0 +1,250 @@
+---
+id: radio
+title: Radio
+---
+import Column from '@site/src/components/Column'
+
+The **Radio** component is an interactive UI element that enables users to make single or multiple selections from a predefined set of options. Users can choose one or more options using radio buttons associated with each value in the set.
+
+
+:::info
+
+Unlike certain other components, the **Radio** component cannot be linked to a qodlysource to display data, instead, options must be configured through the properties panel.
+
+:::
+
+
+## Use Cases
+
+**Radio** component serve various purposes in user interfaces, including:
+
+- **User Preferences**: Select preferred theme, language, or notification settings.
+
+- **Content Filtering**: Filter content based on specific categories or criteriay.
+
+- **Survey Management**: Manage survey questions with predefined answer choices for collecting feedback and insights.
+
+
+
+## Properties Customization
+
+Enhance the **Radio** component to align with your application's requirements using the following customization options:
+
+
+
+
+
Type: Choose the appearance style of the radio buttons. You can select either "Radio" for standard radio buttons or "Tab" for a tab-like appearance.
+
+
+
+
+
+
+
+
+- **Mode**: The Radio Button component offers two distinct selection modes:
+
+
+
+
+
Single Selection: Users can choose only one option at a time, ensuring that their selections are distinct and non-overlapping.
+
Multiple Selection: Users can choose multiple options that are relevant to their needs.
+
+
+
+
+
+
+
+
+
+
+
+
+
Default Value: Set a default value for the radio component, ensuring that a specific option is pre-selected when the component is loaded.
+
+
+
+
+
+
+
+
+- **Options**:
+ - **Adding Options**: Incorporate new options by utilizing the "+" button within the Radio component. Each option can be customized with a label and a corresponding value, enhancing the clarity of user selections.
+
+
+
+
Label: A descriptive text label for each option.
+
Value: A distinct value to each option, enabling effective data handling based on the selections made by users.
+
+
+
+
+
+
+
+ - **Option Duplication**: Duplicate existing options to replicate configurations quickly by clicking on the icon.
+
+ - **Option Removal**: Delete options that no longer serve a purpose by clicking on the icon.
+
+ - **Moving Option**: Arrange options to your preferred position by clicking on the icon
+
+
+
+## Data Integration
+
+The **Radio** component captures and reflects the user's choice within this set.
+
+:::info
+The **Radio** component provides selections from a predefined set of options within the [properties panel](#properties-customization), without relying on qodlysources.
+:::
+
+### Data Binding
+
+To enable data capture for the Radio component, follow these steps:
+
+1. **Navigate to the Properties Panel**: Access the Data Access category located within the Properties panel for the Radio component.
+
+
+
+
+
Define the Qodly Source: Specify the relevant qodlysource that will capture the user's selected choice.
+
+
+
+
+
+
+
+:::tip
+Alternatively, you can establish the connection by dragging and dropping the qodlysource onto the Radio component.
+:::
+
+### Server-Side Interaction
+
+Retrieving user choices is straightforward. By binding a qodlysource to the **Radio** component, you can access and utilize the selected content.
+
+
+
+ For example, when users make a single choice from a set of options, binding a qodlysource captures the selected option.
+ Consequently, you can utilize this option value in various ways, such as within a standard action to initiate a search with matching attribute values.
+
+
+
+
+
+
+Additionally:
+
+:::tip
+The **Radio** component's qodlysource can also be linked to the value of an attribute in the currently selected entity of another qodlysource. This enables the component to automatically display the saved value of the selected option whenever a new entity is chosen.
+:::
+
+## Customizing Radio Styles
+
+The **Radio** component offers additional customization options through CSS, providing control over the appearance of radio elements.
+
+### Understanding Radio CSS Classes
+
+The Radio component supports various CSS classes that enable the customization of radio item appearance. Below is a detailed list of supported CSS classes and the elements they apply to.
+
+| **Class Name** | **Applies To** | **Description** |
+|------------------|-------------------------------|--------------------------------------------|
+| `.fd-radio__item` | The entire set of choices | Styles all the radio elements. |
+| `.selected` | The selected choice | Applies to the radio option that is currently selected. |
+
+### Custom styling examples
+The following examples demonstrate how to customize the Radio component's appearance.
+
+#### Example 1: Shadowed Radio Items
+
+In this example, all radio choices are styled with a white background, rounded corners, and shadow effects to add depth. The text is centered and the color is set to dark blue for contrast.
+
+
+
+
+
+
+
+
+
+
+#### Example 2: Italicized Selected Radio Item
+
+This example customizes the selected radio item by setting its background color to a muted green with a cursive font and italicized style.
+
+
+
+
+
+
+
+
+
+
+#### Example 3: Green Hovered Radio Item
+
+In this example, the hovered radio item is customized with a soft green text and a shadow effect to add depth.
+
+
+
+
+
+
+
+
+
+
+## Showcase
+
+Here's a glimpse of how the **Radio** component will look and behave in action:
+
+
+
+
+## Triggers and Events
+
+The **Radio Button** component primarily responds to the "**On Change**" event, triggering actions when users select different options. This interaction allows you to create dynamic and responsive interfaces based on user choices.
+
diff --git a/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/rangeinput.md b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/rangeinput.md
new file mode 100644
index 000000000..20812176c
--- /dev/null
+++ b/versioned_docs/version-21/4DQodlyPro/pageLoaders/components/rangeinput.md
@@ -0,0 +1,447 @@
+---
+id: rangeinput
+title: Range Input
+---
+import Column from '@site/src/components/Column'
+
+The **Range Input** component is a UI element enabling users to choose a numeric value from a defined range, or enabling developers to represent a value by a cursor positioned within a defined range.
+
+:::info
+
+The **Range Input** component contains embedded elements: **Slider Container**, **Label**, **Filled track**, **Thumb**, and **Track**. This is of great importance as configuring the **Range Input** component may require adjusting properties within the embedded elements. This applies to the visual style, triggers, and actions as they may differ.
+
+:::
+
+
+## Use Cases
+
+**Range Input** component can be applied in various scenarios:
+
+- **Volume Control**: Use it to control audio or video volume levels.
+
+- **Data Filtering**: Implement it to allow users to filter results based on a numeric range, such as prices or quantities.
+
+- **Configuration Settings**: Utilize it for user-configurable settings that require selecting a specific numeric value within a range.
+
+- **Visual representation**: Display a value through a cursor within a range for a visual information.
+
+
+
+## Properties Customization
+
+### Range Input Component
+
+Enhance the **Range Input** component to align with your application's requirements using the following customization options:
+
+
+
+
+
Label Position: Developers can tailor the label's position, placing it above, below, to the left, to the right, or even hidden.
+
+
+
+
+
+
+
+- **Orientation**: Developers can choose between horizontal and vertical orientations for the Matrix. This choice affects the arrangement of Stylebox components and the presence of scrollbars.
+
+
+
+
+
Vertical Orientation (Default): By default, the Range Input component is oriented vertically. This means that Slider container is stacked from top to bottom.
+
Horizontal Orientation: When the Range Input component is configured with a horizontal orientation, Slider container is arranged from left to right.
+
+
+
+
+
+
+
+
+### Embedded Label
+
+Within the **Range Input** component, an embedded **Label** allows for further customization of the following properties:
+
+
+
+
+
Label: Personalize the label to offer clear instructions or guidance.
+
+
+
+
+
+
+
+
+### Embedded Slider Container
+
+Within the **Range Input** component, an embedded **Slider Container** allows for further customization of the following properties:
+
+
+
+
+
Min Value: Set the minimum value that users can select within the range.
+
Max Value: Define the maximum value that users can select within the range.
+
Step: Determine the increment value when users interact with the component.
+
+
+
+
+
+
+
+
+### Embedded Filled track, Thumb, and Track
+
+A slider object consists of these three elements, that can be customized separately:
+
+
+
+They have a default design that can be customized using standard formatting properties, such as **Background color** and **Height/Width**.
+
+The **Display** property allows to control how they are drawn:
+
+
+
+
+
block: standard display, for example the thumb is a 14px white circle by default.
flex: provides extra formatting properties.
none: the element is not displayed; for example, you can hide the thumb
+
+
+
+
+
+
+
+
+
+
+## Data Integration
+
+The **Range Input** component allows for seamless integration of Qodly Sources, enabling dynamic data binding and interaction within the Page.
+
+:::info
+The qodlysource for the **Range Input** component should be a numeric value.
+:::
+
+### Data Binding
+To associate data with the Range Input component, follow these steps:
+
+1. **Navigate to the Properties Panel**: Access the Data Access category located within the Properties panel for the Range Input component.
+
+
+
+
+
Define the Qodly Source: Specify the appropriate qodlysource that contains the data you want to display within the Range Input or retrieve from user input. This can be an attribute from an entity, an array, or a direct qodlysource of type number. For instance, you can select an entity, such as dayplan.hotel_ID.
+
+
+
+
+
+
+
+
+3. **Choose the Attribute**: Choose the specific attribute that you want to display within the component when using an entity or an array type qodlysource, such as dayplan.hotel_ID.rating.
+
+:::tip
+Alternatively, you can establish the connection by dragging and dropping the qodlysource onto the Range Input component.
+:::
+
+### Server-Side Interaction
+
+Interacting with user input data is straightforward. When you bind a qodlysource to the **Range Input** component, you can access and make use of the input content.
+
+Subsequently, you can utilize this input value in various ways, such as within a standard action to initiate a search with matching attribute values.
+
+## Customizing Range Input Styles
+
+The **Range Input** component provides additional customization options through CSS, allowing for a personalized appearance of its elements.
+
+### Component-Specific Classes
+
+The following CSS classes are applied to elements within the **Range Input** component, defining their layout, style, and responsive behavior. Each class can be customized to adjust the look and functionality of specific parts of the component.
+
+| **Class Name** | **Applies To** | **Description** |
+|---------------------------|-----------------------------|----------------------------------------------------------------------------------------------------------------------------|
+| `fd-slidercontainer` | Slider container | Wrapper around the slider track and thumb, controlling padding and spacing between these elements. |
+| `fd-slider` | Main slider container | Defines the main structure of the slider component, setting up basic layout and responsiveness. |
+| `chakra-slider` | Main slider element | Provides styling and positioning for the main slider element, including adjustments for focus and user interaction. |
+| `chakra-slider__track` | Slider track | Styles the visible slider track, controlling background color, height, and positioning of the track element. |
+| `chakra-slider__thumb` | Slider thumb | Styles the draggable thumb, setting properties like size, color, and border radius for the thumb. |
+
+### Component-Specific Tags
+
+The following HTML tags make up the structure of the **Range Input** component. Each tag can be styled individually to adjust its appearance and interaction.
+
+| **Tag Name** | **Applies To** | **Description** |
+|--------------------|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------|
+| `
You can mix indexed placeholders (values directly passed in *value* parameters) and named placeholder values in the same query.|
+|args|object|Parameter(s) to pass to formulas, if any. The **args** object will be received in $1 within formulas and thus its values will be available through *$1.property* (see example 3).|
+|allowFormulas| Boolean|True to allow the formula calls in the query (default). Pass false to disallow formula execution. If set to false and `query()` is given a formula, an error is sent (1278 - formula not allowed in this member method).|
+|queryPlan| Boolean |In the resulting entity selection, returns or does not return the detailed description of the query just before it is executed, i.e. the planned query. The returned property is an object that includes each planned query and subquery (in the case of a complex query). this option is useful during the development phase of an application. It is usually used in conjunction with queryPath. Default if omitted: false. **Note**: this property is supported only by the `entitySelection.query( )` and `dataClass.query( )` functions.|
+|queryPath|Boolean| In the resulting entity selection, returns or does not return the detailed description of the query as it is actually performed. The returned property is an object that contains the actual path used for the query (usually identical to that of the queryPlan, but may differ if the engine manages to optimize the query), as well as the processing time and the number of records found. this option is useful during the development phase of an application. Default if omitted: false. **Note**: this property is supported only by the `entitySelection.query( )` and `dataClass.query( )` functions.|
+
+#### About *queryPlan* and *queryPath*
+
+The information recorded in `queryPlan`/`queryPath` include the query type (indexed and sequential) and each necessary subquery along with conjunction operators. Query paths also contain the number of entities found and the time required to execute each search criterion. You may find it useful to analyze this information while developing your application(s). Generally, the description of the query plan and its path are identical but they can differ because 4D can implement dynamic optimizations when a query is executed in order to improve performance. For example, the 4D engine can dynamically convert an indexed query into a sequential one if it estimates that it is faster. this particular case can occur when the number of entities being searched for is low.
+
+For example, if you execute the following query:
+
+```qs
+ sel = ds.Employee.query("salary < :1 and employer.name == :2 or employer.revenues > :3",\
+ 50000,"Lima West Kilo",10000000,newObject("queryPath",true,"queryPlan",true))
+```
+
+queryPlan:
+
+```
+{Or:[{And:[{item:[index : Employee.salary ] < 50000},
+ {item:Join on Table : Company : Employee.employerID = Company.ID,
+ subquery:[{item:[index : Company.name ] = Lima West Kilo}]}]},
+ {item:Join on Table : Company : Employee.employerID = Company.ID,
+ subquery:[{item:[index : Company.revenues ] > 10000000}]}]}
+```
+
+queryPath:
+
+```
+{steps:[{description:OR,time:63,recordsfounds:1388132,
+ steps:[{description:AND,time:32,recordsfounds:131,
+ steps:[{description:[index : Employee.salary ] < 50000,time:16,recordsfounds:728260},{description:Join on Table : Company : Employee.employerID = Company.ID,time:0,recordsfounds:131,
+ steps:[{steps:[{description:[index : Company.name ] = Lima West Kilo,time:0,recordsfounds:1}]}]}]},{description:Join on Table : Company : Employee.employerID = Company.ID,time:31,recordsfounds:1388132,
+ steps:[{steps:[{description:[index : Company.revenues ] > 10000000,time:0,recordsfounds:933}]}]}]}]}
+```
+
+#### Example 1
+
+this section provides various examples of queries.
+
+Query on a string:
+
+```qs
+entitySelection = ds.Customer.query("firstName == 'S@'")
+```
+
+Query with a NOT statement:
+
+```qs
+entitySelection = ds.Employee.query("not(firstName == Kim)")
+```
+
+Queries with dates:
+
+```qs
+entitySelection = ds.Employee.query("birthDate > :1","1970-01-01")
+entitySelection = ds.Employee.query("birthDate <= :1",currentDate-10950)
+```
+
+Query with indexed placeholders for values:
+
+```qs
+entitySelection = ds.Customer.query("(firstName == :1 or firstName == :2) and (lastName == :3 or lastName == :4)","D@","R@","S@","K@")
+```
+
+Query with indexed placeholders for values on a related dataclass:
+
+```qs
+entitySelection = ds.Employee.query("lastName == :1 and manager.lastName == :2","M@","S@")
+```
+
+Query with indexed placeholder including a descending order by statement:
+
+```qs
+entitySelection = ds.Student.query("nationality == :1 order by campus.name desc, lastname","French")
+```
+
+Query with named placeholders for values:
+
+```qs
+var querySettings : object
+var managedCustomers : cs.CustomerSelection
+querySettings = newObject()
+querySettings.parameters = newObject("userId",1234,"extraInfo",newObject("name","Smith"))
+managedCustomers = ds.Customer.query("salesperson.userId == :userId and name == :extraInfo.name",querySettings)
+```
+
+Query that uses both named and indexed placeholders for values:
+
+```qs
+var querySettings : object
+var managedCustomers : cs.CustomerSelection
+querySettings.parameters = newObject("userId",1234)
+managedCustomers = ds.Customer.query("salesperson.userId == :userId and name == :1","Smith",querySettings)
+```
+
+Query with queryPlan and queryPath objects:
+
+```qs
+entitySelection = ds.Employee.query("(firstName == :1 or firstName == :2) and (lastName == :3 or lastName == :4)","D@","R@","S@","K@",newObject("queryPlan",true,"queryPath",true))
+
+ //you can then get these properties in the resulting entity selection
+var queryPlan, queryPath : object
+queryPlan = entitySelection.queryPlan
+queryPath = entitySelection.queryPath
+```
+
+Query with an attribute path of collection type:
+
+```qs
+entitySelection = ds.Employee.query("extraInfo.hobbies[].name == :1","horsebackriding")
+```
+
+Query with an attribute path of collection type and linked attributes:
+
+```qs
+entitySelection = ds.Employee.query("extraInfo.hobbies[a].name == :1 and extraInfo.hobbies[a].level == :2","horsebackriding",2)
+```
+
+Query with an attribute path of collection type and multiple linked attributes:
+
+```qs
+entitySelection = ds.Employee.query("extraInfo.hobbies[a].name == :1 and
+ extraInfo.hobbies[a].level == :2 and extraInfo.hobbies[b].name == :3 and
+ extraInfo.hobbies[b].level == :4","horsebackriding",2,"Tennis",5)
+```
+
+Query with an attribute path of object type:
+
+```qs
+entitySelection = ds.Employee.query("extra.eyeColor == :1","blue")
+```
+
+Query with an IN statement:
+
+```qs
+entitySelection = ds.Employee.query("firstName in :1",newCollection("Kim","Dixie"))
+```
+
+Query with a NOT (IN) statement:
+
+```qs
+entitySelection = ds.Employee.query("not (firstName in :1)",newCollection("John","Jane"))
+```
+
+Query with indexed placeholders for attributes:
+
+```qs
+var es : cs.EmployeeSelection
+es = ds.Employee.query(":1 == 1234 and :2 == 'Smith'","salesperson.userId","name")
+ //salesperson is a related entity
+```
+
+Query with indexed placeholders for attributes and named placeholders for values:
+
+```qs
+var es : cs.EmployeeSelection
+var querySettings : object
+querySettings = newObject()
+querySettings.parameters = newObject("customerName","Smith")
+es = ds.Customer.query(":1 == 1234 and :2 == :customerName","salesperson.userId","name",querySettings)
+ //salesperson is a related entity
+```
+
+Query with indexed placeholders for attributes and values:
+
+
+```qs
+var es : cs.EmployeeSelection
+es = ds.Clients.query(":1 == 1234 and :2 == :3","salesperson.userId","name","Smith")
+ //salesperson is a related entity
+```
+
+#### Example 2
+
+this section illustrates queries with named placeholders for attributes.
+
+Given an Employee dataclass with 2 entities:
+
+Entity 1:
+
+```qs
+name: "Marie"
+number: 46
+softwares:{
+"Word 10.2": "Installed",
+"Excel 11.3": "To be upgraded",
+"Powerpoint 12.4": "Not installed"
+}
+```
+
+Entity 2:
+
+```qs
+name: "Sophie"
+number: 47
+softwares:{
+"Word 10.2": "Not installed",
+"Excel 11.3": "To be upgraded",
+"Powerpoint 12.4": "Not installed"
+}
+```
+
+Query with named placeholders for attributes:
+
+```qs
+ var querySettings : object
+ var es : cs.EmployeeSelection
+ querySettings = newObject()
+ querySettings.attributes = newObject("attName","name","attWord",newCollection("softwares","Word 10.2"))
+ es = ds.Employee.query(":attName == 'Marie' and :attWord == 'Installed'",querySettings)
+ //es.length = 1 (Employee Marie)
+```
+
+Query with named placeholders for attributes and values:
+
+```qs
+ var querySettings : object
+ var es : cs.EmployeeSelection
+ var name : string
+ querySettings = newObject()
+ //Named placeholders for values
+ querySettings.parameters = newObject("givenName",name)
+ //Named placeholders for attribute paths
+ querySettings.attributes = newObject("attName","name")
+ es = ds.Employee.query(":attName == :givenName",querySettings)
+```
+
+#### Example 3
+
+These examples illustrate the various ways to use formulas with or without parameters in your queries.
+
+The formula is given as text with `eval()` in the *queryString* parameter:
+
+```qs
+ var es : cs.StudentsSelection
+ es = ds.Students.query("eval(length(this.lastname) >= 30) and nationality == 'French'")
+```
+
+The formula is given as a `formula` object through a placeholder:
+
+```qs
+ var es : cs.StudentsSelection
+ var aform : object
+ aform = formula(length(this.lastname)>= 30)
+ es = ds.Students.query(":1 and nationality == 'French'",aform)
+```
+
+Only a `formula` object is given as criteria:
+
+```qs
+ var es : cs.StudentsSelection
+ var aform : object
+ aform = formula(length(this.lastname)>= 30)
+ es: = ds.Students.query(aform)
+```
+
+Several formulas can be applied:
+
+```qs
+ declare(formula1 : 4D.Function) -> result : cs.StudentsSelection
+ var formula2 : 4D.Function
+ formula2 = formula(length(this.firstname)>= 30)
+ result = ds.Students.query(":1 and :2 and nationality == 'French'",formula1,formula2)
+```
+
+
+A text formula in *queryString* receives a parameter:
+
+```qs
+ var es : cs.StudentsSelection
+ var settings : object
+ settings = newObject()
+ settings.args = newObject("filter";"-")
+ es = ds.Students.query("eval(checkName($1.filter)) and nationality == :1","French",settings)
+```
+
+```qs
+ //checkName method
+ declare(exclude : string) -> result : Boolean
+ result = (Position(exclude,this.lastname) = 0)
+```
+
+Using the same **checkName** method, a `formula` object as placeholder receives a parameter:
+
+```qs
+ var es : cs.StudentsSelection
+ var settings : object
+ var aformula : 4D.Function
+ aformula = formula(checkName($1.filter))
+ settings = newObject()
+ settings.args = newObject("filter","-")
+ es = ds.Students.query(":1 and nationality == :2",aformula,"French",settings)
+ settings.args.filter = "*" // change the parameters without updating the aformula object
+ es = ds.Students.query(":1 and nationality == :2",aformula,"French",settings)
+```
+
+We want to disallow formulas, for example when the user enters their query:
+
+```qs
+ var es : cs.StudentsSelection
+ var settings : object
+ var queryString : string
+ // queryString is entered by the user
+ settings = newObject("allowFormulas",false)
+ es = ds.Students.query(queryString,settings) //An error is raised if queryString contains a formula
+```
+
+#### See also
+
+[`.query()`](EntitySelectionClass.md#query) for entity selections
+
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/DataStoreClass.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/DataStoreClass.md
new file mode 100644
index 000000000..b02e877e5
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/DataStoreClass.md
@@ -0,0 +1,246 @@
+---
+id: DataStoreClass
+title: DataStore
+---
+
+A [Datastore](../qodlyScript/guides/data-model.md#datastore) is the interface object provided by ORDA to reference and access a database. A `Datastore` object is returned by the [`ds`](commands/ds.md) command (a shortcut to the main datastore) or the [`openDatastore`](commands/openDatastore.md) command.
+
+
+
+### Functions and properties
+
+||
+|---|
+|[](#canceltransaction) |
+|[](#dataclassname) |
+|[](#isadminprotected) |
+|[](#setadminprotection) |
+|[](#starttransaction) |
+|[](#validatetransaction) |
+
+
+
+
+
+
+## .cancelTransaction()
+
+
+
+**.cancelTransaction**()
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+||||Does not require any parameters|
+
+
+
+#### Description
+
+The `.cancelTransaction()` function cancels the transaction opened by the [`.startTransaction()`](#starttransaction) function at the corresponding level in the current process for the specified datastore.
+
+The `.cancelTransaction()` function cancels any changes made to the data during the transaction.
+
+You can nest several transactions (sub-transactions). If the main transaction is cancelled, all of its sub-transactions are also cancelled, even if they were validated individually using the [`.validateTransaction()`](#validatetransaction) function.
+
+
+#### Example
+
+See example for the [`.startTransaction()`](#starttransaction) function.
+
+
+
+
+
+
+## *.dataclassName*
+
+
+***.dataclassName*** : 4D.DataClass
+
+
+#### Description
+
+Each dataclass in a datastore is available as a property of the [DataStore object](../qodlyScript/guides/data-model.md#datastore). The returned cs.object contains all attributes of the dataclass as objects.
+
+
+#### Example
+
+
+```qs
+ var emp : cs.Employee
+ var sel : cs.EmployeeSelection
+ emp = ds.Employee //emp contains the Employee dataclass
+ sel = emp.all() //gets an entity selection of all employees
+
+ //you could also write directly:
+ sel = ds.Employee.all()
+```
+
+
+
+
+
+
+
+
+
+
+## .isAdminProtected()
+
+
+**.isAdminProtected**() : boolean
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|boolean|←|true if the Data Explorer access is disabled, false if it is enabled (default)|
+
+
+
+#### Description
+
+The `.isAdminProtected()` function returns `true` if Data Explorer access has been disabled for the working session.
+
+By default, the Data Explorer access is granted for `webAdmin` sessions, but it can be disabled to prevent any data access from administrators (see the [`.setAdminProtection()`](#setadminprotection) function).
+
+#### See also
+
+[`.setAdminProtection()`](#setadminprotection)
+
+
+
+
+
+
+## .setAdminProtection()
+
+
+**.setAdminProtection**( *status* : boolean )
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|status|boolean|→|true to disable Data Explorer access to data on the `webAdmin` port, false (default) to grant access|
+
+
+
+#### Description
+
+The `.setAdminProtection()` function allows disabling any data access on the [web admin port](https://developer.4d.com/docs/en/Admin/webAdmin.html#webadmin-settings), including for the [Data Explorer](https://developer.4d.com/docs/en/Admin/dataExplorer.html) in `WebAdmin` sessions.
+
+By default when the function is not called, access to data is always granted on the web administration port for a session with `WebAdmin` privilege using the Data Explorer. In some configurations, for example when the application server is hosted on a third-party machine, you might not want the administrator to be able to view your data, although they can edit the server configuration, including the [access key](https://developer.4d.com/docs/en/Admin/webAdmin.html#access-key) settings.
+
+In this case, you can call this function to disable the data access from Data Explorer on the web admin port of the machine, even if the user session has the `WebAdmin` privilege. When this function is executed, the data file is immediately protected and the status is stored on disk: the data file will be protected even if the application is restarted.
+
+
+#### Example
+
+You create a *protectDataFile* project method to call before deployments for example:
+
+```qs
+ ds.setAdminProtection(true) //Disables the Data Explorer data access
+```
+
+#### See also
+
+[`.isAdminProtected()`](#isadminprotected)
+
+
+
+
+
+
+
+
+## .startTransaction()
+
+
+
+**.startTransaction**()
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+||||Does not require any parameters|
+
+
+
+#### Description
+
+The `.startTransaction()` function starts a transaction in the current process on the database matching the datastore to which it applies. Any changes made to the datastore's entities in the transaction's process are temporarily stored until the transaction is either validated or cancelled.
+
+>If this method is called on the main datastore (i.e. the datastore returned by the `ds` command), the transaction is applied to all operations performed on the main datastore and on the underlying database.
+
+You can nest several transactions (sub-transactions). Each transaction or sub-transaction must eventually be cancelled or validated. Note that if the main transaction is cancelled, all of its sub-transactions are also cancelled even if they were validated individually using the `.validateTransaction()` function.
+
+
+:::info
+
+ A transaction represents a series of changes made within a context on interconnected data. A transaction is only permanently saved in the datastore when the transaction is validated as a whole by calling [`.validateTransaction()`](#validatetransaction). If a transaction has not been validated, whether it was cancelled or because of some external event, the changes are not saved.
+
+:::
+
+
+#### Example
+
+
+
+```qs
+ var connect,status : object
+ var person : cs.PersonsEntity
+ var ds : cs.DataStore
+ var choice : string
+ var error : boolean
+
+ ds.startTransaction()
+ person = ds.Persons.query("lastname == :1","Peters").first()
+
+ if(person != null)
+ person.lastname = "Smith"
+ status = person.save()
+ end
+ ...
+ ...
+ if(lastErrors[0].errCode != 0)
+ ds.cancelTransaction()
+ else
+ ds.validateTransaction()
+ end
+```
+
+
+
+
+
+
+
+## .validateTransaction()
+
+
+
+**.validateTransaction**()
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+||||Does not require any parameters|
+
+
+
+#### Description
+
+The `.validateTransaction()` function accepts the transaction that was started with [`.startTransaction()`](#starttransaction) at the corresponding level.
+
+The function saves the changes to the data that occurred during the transaction.
+
+You can nest several transactions (sub-transactions). If the main transaction is cancelled, all of its sub-transactions are also cancelled, even if they were validated individually using this function.
+
+
+#### Example
+
+See example for [`.startTransaction()`](#starttransaction).
+
+
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/Directory.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/Directory.md
new file mode 100644
index 000000000..6bba984e4
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/Directory.md
@@ -0,0 +1,448 @@
+---
+id: Directory
+title: Directory Class
+---
+
+
+
+
+## .exists
+
+
+**.exists** : boolean
+
+#### Description
+
+The `.exists` property returns true if the folder exists on disk, and false otherwise.
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .extension
+
+
+**.extension** : string
+
+#### Description
+
+The `.extension` property returns the extension of the folder name (if any). An extension always starts with ".". The property returns an empty string if the folder name does not have an extension.
+
+This property is **read-only**.
+
+
+
+
+
+---
+
+
+## .fullName
+
+**.fullName** : string
+
+#### Description
+
+The `.fullName` property returns the full name of the folder, including its extension (if any).
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .hidden
+
+
+**.hidden** : boolean
+
+#### Description
+
+The `.hidden` property returns true if the folder is set as "hidden" at the system level, and false otherwise.
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .isAlias
+
+**.isAlias** : boolean
+
+#### Description
+
+The `.isAlias` property returns always **false** for a `Folder` object.
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .isFile
+
+
+**.isFile** : boolean
+
+#### Description
+
+The `.isFile` property returns always **false** for a folder.
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .isFolder
+
+
+**.isFolder** : boolean
+
+#### Description
+
+The `.isFolder` property returns always **true** for a folder.
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .isPackage
+
+
+**.isPackage** : boolean
+
+#### Description
+
+The `.isPackage` property returns true if the folder is a package on macOS (and exists on disk). Otherwise, it returns false.
+
+On Windows, `.isPackage` always returns **false**.
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .modificationDate
+
+**.modificationDate** : date
+
+#### Description
+
+The `.modificationDate` property returns the date of the folder's last modification.
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .modificationTime
+
+
+**.modificationTime** : time
+
+#### Description
+
+The `.modificationTime` property returns the time of the folder's last modification (expressed as a number of seconds beginning at 00:00).
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .name
+
+
+**.name** : string
+
+#### Description
+
+The `.name` property returns the name of the folder, without extension (if any).
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .original
+
+
+**.original** : 4D.Folder
+
+#### Description
+
+The `.original` property returns the same Folder object as the folder.
+
+This property is **read-only**.
+
+>This property is available on folders to allow generic code to process folders or files.
+
+
+
+---
+
+
+## .parent
+
+
+**.parent** : 4D.Folder
+
+#### Description
+
+The `.parent` property returns the parent folder object of the folder. If the path represents a system path (e.g., "/DATA/"), the system path is returned.
+
+If the folder does not have a parent (root), the null value is returned.
+
+This property is **read-only**.
+
+
+
+---
+
+
+## .path
+
+
+**.path** : string
+
+#### Description
+
+The `.path` property returns the POSIX path of the folder. If the path represents a filesystem (e.g., "/DATA/"), the filesystem is returned.
+
+This property is **read-only**.
+
+
+
+
+---
+
+
+## .copyTo()
+
+
+**.copyTo**( *destinationFolder* : 4D.Folder \{ , *newName* : string \} \{ , *overwrite* : integer \} ) : 4D.Folder
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|destinationFolder |4D.Folder |→|Destination folder|
+|newName|string|→|Name for the copy|
+|overwrite|integer|→|`fk overwrite` to replace existing elements|
+|Result|4D.Folder|←|Copied file or folder|
+
+#### Description
+
+The `.copyTo()` function copies the `Folder` object into the specified *destinationFolder*.
+
+The *destinationFolder* must exist on disk, otherwise an error is generated.
+
+By default, the folder is copied with the name of the original folder. If you want to rename the copy, pass the new name in the *newName* parameter. The new name must comply with naming rules (e.g., it must not contain characters such as ":", "/", etc.), otherwise an error is returned.
+
+If a folder with the same name already exists in the *destinationFolder*, by default Qodly generates an error. You can pass the `kOverwrite` constant in the *overwrite* parameter to ignore and overwrite the existing file
+
+|Constant|Value|Comment|
+|---|---|---|
+|`kOverwrite`|4|Overwrite existing elements, if any|
+
+**Returned value**
+
+The copied `Folder` object.
+
+#### Example
+
+You want to copy a Pictures *folder* from the resources folder to the data folder:
+
+```qs
+var userImages, copiedImages : 4D.Folder
+userImages = folder("/SOURCES/Shared/Pictures/")
+copiedImages = userImages.copyTo(folder("/DATA"),kOverwrite)
+```
+
+
+
+---
+
+
+## .file()
+
+**.file**( *path* : string ) : 4D.File
+
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|path|string|→|Relative POSIX file pathname|
+|Result|4D.File|←|`File` object (null if invalid path)|
+
+#### Description
+
+The `.file()` function creates a `File` object inside the `Folder` object and returns its reference.
+
+In *path*, pass a relative POSIX path to designate the file to return. The path will be evaluated from the parent folder as root.
+
+**Returned value**
+
+A `File` object or null if *path* is invalid.
+
+#### Example
+
+```qs
+var myPDF : 4D.File
+myPDF = folder("/DATA").file("Pictures/info.pdf")
+```
+
+
+
+---
+
+
+## .files()
+
+
+**.files**( \{ *options* : integer \} ) : collection
+
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|options|integer|→|File list options|
+|Result|collection|←|collection of children file objects|
+
+#### Description
+
+The `.files()` function returns a collection of `File` objects contained in the folder.
+
+>Aliases or symbolic links are not resolved.
+
+By default, if you omit the *options* parameter, only the files at the first level of the folder are returned in the collection, as well as invisible files or folders. You can modify this by passing, in the *options* parameter, one or more of the following constants:
+
+|Constant| Value| Comment|
+|---|---|---|
+|`kRecursive`|1|The collection contains files of the specified folder and its subfolders|
+|`kIgnoreInvisible`| 8|Invisible files are not listed|
+
+**Returned value**
+
+Collection of `file` objects.
+
+#### Example 1
+
+You want to know if there are invisible files in the project folder:
+
+```qs
+ var all, noInvisible : collection
+ var info : string
+ all = folder("/PACKAGE").files()
+ noInvisible = folder("/PACKAGE").files(kIgnoreInvisible)
+ if(all.length != noInvisible.length)
+ info = "Project folder contains hidden files."
+ end
+```
+
+#### Example 2
+
+You want to get all files that are not invisible in the Resources folder:
+
+```qs
+ var recursive : collection
+ recursive = folder("/RESOURCES").files(kRecursive+kIgnoreInvisible)
+```
+
+
+
+---
+
+
+## .folder()
+
+
+**.folder**( *path* : string ) : 4D.Folder
+
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|path|string|→|Relative POSIX file pathname|
+|Result|4D.Folder|←|Created folder object (null if invalid *path*)|
+
+#### Description
+
+The `.folder()` function creates a `folder` object inside the parent `folder` object and returns its reference.
+
+In *path*, pass a relative POSIX path to designate the folder to return. The path will be evaluated from the parent folder as root.
+
+**Returned value**
+
+A `folder` object or null if *path* is invalid.
+
+#### Example
+
+```qs
+ var mypicts : 4D.Folder
+ mypicts = folder("/RESOURCES").folder("Pictures")
+```
+
+
+
+---
+
+
+## .folders()
+
+
+**.folders**( \{ *options* : integer \} ) : collection
+
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|options|integer|→|Folder list options|
+|Result|collection|←|Collection of children folder objects|
+
+#### Description
+
+The `.folders()` function returns a collection of `folder` objects contained in the parent folder.
+
+By default, if you omit the *options* parameter, only the folders at the first level of the folder are returned in the collection. You can modify this by passing, in the *options* parameter, one or more of the following constants:
+
+|Constant| Value| Comment|
+|---|---|---|
+|`kRecursive`| 1|The collection contains folders of the specified folder and its subfolders|
+|`kIgnoreInvisible`| 8|Invisible folders are not listed|
+
+**Returned value**
+
+collection of `folder` objects.
+
+#### Example
+
+You want the collection of all folders and subfolders of the database folder:
+
+```qs
+ var allFolders : collection
+ allFolders = folder("/PACKAGE").folders(kRecursive)
+```
+
+
+
+---
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/Document.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/Document.md
new file mode 100644
index 000000000..6f3aa88f4
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/Document.md
@@ -0,0 +1,402 @@
+---
+id: Document
+title: Document Class
+---
+
+## Description
+
+
+
+## .exists
+
+
+**.exists** : boolean
+
+#### Description
+
+The `.exists` property returns true if the file exists on disk, and false otherwise.
+
+This property is **read-only**.
+
+
+
+
+
+## .extension
+
+
+**.extension** : string
+
+#### Description
+
+The `.extension` property returns the extension of the file name (if any). An extension always starts with ".". The property returns an empty string if the file name does not have an extension.
+
+This property is **read-only**.
+
+
+
+
+## .fullName
+
+
+**.fullName** : string
+
+#### Description
+
+The `.fullName` property returns the full name of the file, including its extension (if any).
+
+This property is **read-only**.
+
+
+
+
+## .hidden
+
+
+
+**.hidden** : boolean
+
+#### Description
+
+The `.hidden` property returns true if the file is set as "hidden" at the system level, and false otherwise.
+
+This property is **read/write**.
+
+
+
+
+## .isAlias
+
+**.isAlias** : boolean
+
+#### Description
+
+The `.isAlias` property returns true if the file is an alias, a shortcut, or a symbolic link, and false otherwise.
+
+This property is **read-only**.
+
+
+
+
+## .isFile
+
+
+**.isFile** : boolean
+
+#### Description
+
+The `.isFile` property returns always true for a file.
+
+This property is **read-only**.
+
+
+
+
+## .isFolder
+
+
+**.isFolder** : boolean
+
+#### Description
+
+The `.isFolder` property returns always false for a file.
+
+This property is **read-only**.
+
+
+
+
+## .isWritable
+
+
+**.isWritable** : boolean
+
+#### Description
+
+The `.isWritable` property returns true if the file exists on disk and is writable.
+
+>The property checks the ability of the application to write on the disk (access rights), it does not solely rely on the *writable* attribute of the file.
+
+This property is **read-only**.
+
+**Example**
+
+```qs
+ myFile = file("/SOURCES/Shared/Archives/ReadMe.txt")
+ if(myFile.isWritable)
+ myNewFile = myFile.setText("Added text")
+ end
+```
+
+
+
+
+## .modificationDate
+
+
+**.modificationDate** : date
+
+#### Description
+
+The `.modificationDate` property returns the date of the file's last modification.
+
+This property is **read-only**.
+
+
+
+
+## .modificationTime
+
+
+**.modificationTime** : time
+
+##### Description
+
+The `.modificationTime` property returns the time of the file's last modification (expressed as a number of seconds beginning at 00:00).
+
+This property is **read-only**.
+
+
+
+
+## .name
+
+
+**.name** : string
+
+#### Description
+
+The `.name` property returns the name of the file without extension (if any).
+
+This property is **read-only**.
+
+
+
+
+## .original
+
+
+**.original** : 4D.File **.original** : 4D.Folder
+
+#### Description
+
+The `.original` property returns the target element for an alias, a shortcut, or a symbolic link file. The target element can be:
+
+* a file object
+* a folder object
+
+For non-alias files, the property returns the same file object as the file.
+
+This property is **read-only**.
+
+
+
+
+## .parent
+
+
+**.parent** : 4D.Folder
+
+#### Description
+
+The `.parent` property returns the parent folder object of the file. If the path represents a system path (e.g., "/DATA/"), the system path is returned.
+
+This property is **read-only**.
+
+
+
+
+## .path
+
+**.path** : string
+
+#### Description
+
+The `.path` property returns the POSIX path of the file. If the path represents a filesystem (e.g., "/DATA/"), the filesystem is returned.
+
+This property is **read-only**.
+
+
+
+
+
+## .size
+
+
+**.size** : number
+
+#### Description
+
+The `.size` property returns the size of the file expressed in bytes. If the file does not exist on disk, the size is 0.
+
+This property is **read-only**.
+
+
+
+
+
+## .copyTo()
+
+
+**.copyTo**( *destinationFolder* : 4D.Folder \{ , *newName* : string \} \{ , *overwrite* : integer \} ) : 4D.File
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|destinationFolder | 4D.Folder |→|Destination folder|
+|newName|string|→|Name for the copy|
+|overwrite|integer|→|`kOverwrite` to replace existing elements|
+|Result|4D.File|←|Copied file|
+
+#### Description
+
+The `.copyTo()` function copies the `file` object into the specified *destinationFolder* .
+
+The *destinationFolder* must exist on disk, otherwise an error is generated.
+
+By default, the file is copied with the name of the original file. If you want to rename the copy, pass the new name in the *newName* parameter. The new name must comply with naming rules (e.g., it must not contain characters such as ":", "/", etc.), otherwise an error is returned.
+
+If a file with the same name already exists in the *destinationFolder*, by default 4D generates an error. You can pass the `kOverwrite` constant in the *overwrite* parameter to ignore and overwrite the existing file
+
+|Constant|Value|Comment|
+|---|---|---|
+|`kOverwrite`|4|Overwrite existing elements, if any|
+
+**Returned value**
+
+The copied `file` object.
+
+#### Example
+
+You want to copy a picture *file* from the user's document folder to the application folder:
+
+```qs
+var source, copy : 4D.File
+source = file("/SOURCES/Shared/Pictures/photo.png")
+copy = source.copyTo(folder("/PACKAGE"),kOverwrite)
+```
+
+
+
+
+## .getContent()
+
+
+**.getContent**() : 4D.Blob
+
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|Result | 4D.Blob |←|File content|
+
+#### Description
+
+The `.getContent()` function returns a `4D.Blob` object containing the entire content of a file. For information on blobs, please refer to the [blob](basics/lang-blob.md) section.
+
+**Returned value**
+
+A `4D.Blob` object.
+
+#### Example
+
+To save a document's contents in a `Blob` attribute:
+
+```qs
+ var myFile : 4D.File
+ var vEntity : cs.myClassEntity
+
+ myFile = file("/SOURCES/Shared/Archives/data.txt")
+ vEntity = ds.myClass.all().first() //get an entity
+ vEntity.infoBlob = myFile.getContent()
+ vEntity.save()
+
+```
+
+
+
+
+
+## .getText()
+
+
+**.getText**( \{ *charSetName* : string \{ , *breakMode* : integer \} \} ) : string **.getText**( \{ *charSetNum* : integer \{ , *breakMode* : integer \} \} ) : string
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|charSetName |string |→ |Name of character set|
+|charSetNum |integer |→ |Number of character set|
+|breakMode|integer |→ |Processing mode for line breaks|
+|Result |string |← |String from the document|
+
+#### Description
+
+The `.getText()` function returns the contents of the file as text.
+
+Optionally, you can designate the character set to be used for reading the contents. You can pass either:
+
+* in *charSetName*, a string containing the standard set name (for example "ISO-8859-1" or "UTF-8"),
+* or in *charSetNum*, the MIBEnum ID (number) of the standard set name.
+
+> For the list of character sets supported by Qodly, refer to the description of the `convertFromText` command.
+
+If the document contains a Byte Order Mark (BOM), Qodly uses the character set that it has set instead of the one specified in *charSetName* or *charSetNum* (this parameter is then ignored).
+If the document does not contain a BOM and if *charSetName* or *charSetNum* is omitted, by default Qodly uses the "UTF-8" character set.
+
+In *breakMode*, you can pass a number indicating the processing to apply to end-of-line characters in the document. The following constants of the "System Documents" theme are available:
+
+|Constant | Value| Comment|
+|---|---|---|
+|`kDocumentUnchanged`|0|No processing|
+|`kDocumentWithNativeFormat`|1|(Default) Line breaks are converted to the native format of the operating system: CR (carriage return) under Unix, CRLF (carriage return + line feed) under Windows|
+|`kDocumentWithCRLF`|2|Line breaks are converted to Windows format: CRLF (carriage return + line feed)|
+|`kDocumentWithCR`|3|Line breaks are converted to OS X format: CR (carriage return)|
+|`kDocumentWithLF`|4|Line breaks are converted to Unix format: LF (line feed)|
+
+By default, when you omit the *breakMode* parameter, line breaks are processed in native mode (1).
+
+**Returned value**
+
+Text of the file.
+
+#### Example
+
+Given the following text document (fields are separated by tabs):
+
+```qs
+id name price vat
+3 thé 1.06€ 19.6
+2 café 1.05€ 19.6
+```
+
+When you execute this code:
+
+```qs
+ var myFile : 4D.File
+ var txt : string
+ myFile = file("/SOURCES/Shared/Billing.txt") //UTF-8 by default
+ txt = myFile.getText()
+```
+
+... you get the following for `txt`:
+
+"id\tname\tprice\tvat\r\n3\tthé\t1.06€\t19.6\r\n2\tcafé\t1.05€\t19.6"
+
+with `\t` (tab) as separator and `\r\n` (CRLF) as line delimiter.
+
+Here is another example with the same file, but a different line delimiter:
+
+```qs
+ txt = myFile.getText("UTF-8", kDocumentWithLF)
+```
+
+In this case, the contents of `txt` are as follows:
+
+"id\tname\tprice\tvat\n3\tthé\t1.06€\t19.6\n2\tcafé\t1.05€\t19.6"
+
+This time `\n` (LF) is used as line delimiter.
+
+
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/EmailObjectClass.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/EmailObjectClass.md
new file mode 100644
index 000000000..ec8335eba
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/EmailObjectClass.md
@@ -0,0 +1,364 @@
+---
+id: EmailObjectClass
+title: Email
+---
+
+Creating, sending or receiving emails in Qodly is done by handling an `Email` object.
+
+`Email` objects are created when receiving mails through a *transporter* class function:
+
+- IMAP - [`.getMail()`](IMAPTransporterClass.md#getmail) and [`.getMails()`](IMAPTransporterClass.md#getmails) functions to get emails from an IMAP server
+- POP3 - [`.getMail()`](POP3TransporterClass.md#getmail) function to get an email from a POP3 server.
+
+> You can also create a new, blank `Email` object by calling the `New object` command, and then fill it with [Email object properties](#properties).
+
+You send `Email` objects using the SMTP [`.send()`](SMTPTransporterClass.md#send) function.
+
+[`mailConvertFromMIME`](commands/mailConvertFromMIME.md) and [`mailConvertToMIME`](commands/mailConvertToMIME.md) commands can be used to convert `Email` objects to and from MIME contents.
+
+
+### Properties
+
+`Email` objects provide the following properties:
+
+> QodlyScript follows the [JMAP specification](https://jmap.io/spec-mail.html) to format the `Email` object.
+
+||
+|---|
+|[](#attachments) |
+|[](#bcc) |
+|[](#bodystructure) |
+|[](#bodyvalues) |
+|[](#cc) |
+|[](#comments) |
+|[](#from) |
+|[](#headers) |
+|[](#htmlbody) |
+|[](#id) |
+|[](#inreplyto) |
+|[](#keywords) |
+|[](#messageid) |
+|[](#receivedat) |
+|[](#references) |
+|[](#replyto) |
+|[](#sendat) |
+|[](#sender) |
+|[](#size) |
+|[](#subject) |
+|[](#stringbody) |
+|[](#to) |
+
+### Email Addresses
+
+All properties that contain email addresses ([`from`](#from), [`cc`](#cc), [`bcc`](#bcc), [`to`](#to), [`sender`](#sender), [`replyTo`](#replyto)) accept a value of string, object, or collection type.
+
+#### String
+
+- single email: "somebody@domain.com"
+- single display name+email: "Somebody <somebody@domain.com>;"
+- several emails: "Somebody <somebody@domain.com>;,me@home.org"
+
+#### object
+
+An object with two properties:
+
+|Property|Type|Description|
+|---|---|---|
+|name|string|Display name (can be null)|
+|email|string|Email address|
+
+#### collection
+
+A collection of address objects.
+
+### Handling body part
+
+The [`stringBody`](#stringbody) and [`htmlBody`](#htmlbody) properties are only used with the `SMTP.send()` function to allow sending simple mails. When both property are filled, the MIME content-type multipart/alternative is used. The email client should then recognize the multipart/alternative part and display the string part or html part as necessary.
+
+[`bodyStructure`](#bodystructure) and [`bodyValues`](#bodyvalues) are used for `SMTP` when the [Email object](#properties) is built from a MIME document, e.g. when generated by the `MAIL Convert from MIME` command. In this case, both `bodyStructure` and `bodyValues` properties must be passed together, and it is not recommended to use `stringBody` and `htmlBody`.
+
+#### Example of bodyStructure and bodyValues objects
+
+```json
+"bodyStructure": {
+ "type": "multipart/mixed",
+ "subParts": [
+ {
+ "partId": "p0001",
+ "type": "string/plain"
+ },
+ {
+ "partId": "p0002",
+ "type": "string/html"
+ }
+ ]
+},
+"bodyValues": {
+ "p0001": {
+ "value": "I have the most brilliant plan. Let me tell you all about it."
+ },
+ "p0002": {
+ "value": "
I have the most brilliant plan. Let me tell you all about it.
"
+ }
+}
+```
+
+
+
+## .attachments
+
+**.attachments** : collection
+
+#### Description
+
+The `.attachments` property contains a collection of `4D.MailAttachment` object(s).
+
+Attachment objects are defined through the [`mailNewAttachment`](MailAttachmentClass.md#4dmailattachmentnew) command. Attachment objects have specific [properties and functions](MailAttachmentClass.md).
+
+## .bcc
+
+**.bcc** : string **.bcc** : object **.bcc** : collection
+
+#### Description
+
+The `.bcc` property contains the Blind Carbon Copy (BCC) hidden email recipient [addresse(s)](#email-addresses) of the email.
+
+## .bodyStructure
+
+**.bodyStructure** : object
+
+#### Description
+
+The `.bodyStructure` property contains the *EmailBodyPart* object, i.e. the full MIME structure of the message body (optional). See [Handling body part](#handling-body-part) section.
+
+The `.bodyStructure` object contains the following properties:
+
+|Property|Type|Value|
+|---|---|---|
+|partID|string|Identifies the part uniquely within the email|
+|type|string|(mandatory) Value of the Content-Type header field of the part|
+|charset|string|Value of the charset parameter of the Content-Type header field|
+|encoding|string|If `isEncodingProblem == true`, the Content-Transfer-Encoding value is added (by default undefined)|
+|disposition|string|Value of the Content-Disposition header field of the part|
+|language|collection of strings|List of language tags, as defined in [RFC3282](https://tools.ietf.org/html/rfc3282), in the Content-Language header field of the part, if present.|
+|location|string|URI, as defined in [RFC2557](https://tools.ietf.org/html/rfc2557), in the Content-Location header field of the part, if present.|
+|subParts|collection of objects|Body parts of each child (collection of *EmailBodyPart* objects)|
+|headers|collection of objects|List of all header fields in the part, in the order they appear in the message (collection of *EmailHeader* objects, see [headers](#headers) property)|
+
+## .bodyValues
+
+**.bodyValues** : object
+
+#### Description
+
+The `.bodyValues` property contains the *EmailBodyValue* object, containing an object for each *partID* of `bodyStructure` (optional). See [Handling body part](#handling-body-part) section.
+
+The `.bodyValues` object contains the following properties:
+
+|Property|Type|Value|
+|---|---|---|
+|*partID*.value|string|Value of the body part|
+|*partID*.isEncodingProblem|boolean|True if malformed sections are found while decoding the charset, or unknown charset, or unknown content transfer-encoding. False by default|
+
+## .cc
+
+**.cc** : string **.cc** : object **.cc** : collection
+
+#### Description
+
+The `.cc` property contains the Carbon Copy (CC) additional email recipient [addresse(s)](#email-addresses) of the email.
+
+## .comments
+
+**.comments** : string
+
+#### Description
+
+The `.comments` property contains an additional comments header.
+
+Comments only appear within the header section of the message (keeping the message's body untouched).
+
+For specific formatting requirements, please consult the [RFC#5322](https://tools.ietf.org/html/rfc5322).
+
+## .from
+
+**.from** : string **.from** : object **.from** : collection
+
+#### Description
+
+The `.from` property contains the originating [address(es)](#email-addresses) of the email.
+
+Each email you send out has both the [sender](#sender) and **from** addresses:
+
+- the sender domain is what the receiving email server gets when opening the session,
+- the from address is what the recipient(s) will see.
+
+For better deliverability, it is recommended to use the same from and sender addresses.
+
+## .headers
+
+**.headers** : collection
+
+#### Description
+
+The `.headers` property contains a collection of `EmailHeader` objects, in the order they appear in the message. This property allows users to add extended (registered) headers or user-defined (not registered, starting with "X") headers.
+
+> If an `EmailHeader` object property defines a header such as "from" or "cc" which is already set as a property at the mail level, the `EmailHeader` property is ignored.
+
+Every object of the headers collection can contain the following properties:
+
+|Property|Type|Value|
+|---|---|---|
+|[].name|string|(mandatory) Header field name as defined in [RFC#5322](https://tools.ietf.org/html/rfc5322). If null or undefined, the header field is not added to the MIME header.|
+|[].value|string|Header field values as defined in [RFC#5322](https://tools.ietf.org/html/rfc5322)|
+
+## .htmlBody
+
+**.htmlBody** : string
+
+#### Description
+
+The `.htmlBody` property contains the HTML representation of the email message (default charset is UTF-8) (optional, SMTP only). See [Handling body part](#handling-body-part) section.
+
+## .id
+
+**.id** : string
+
+#### Description
+
+[IMAP transporter](IMAPTransporterClass.md) only.
+
+The `.id` property contains the unique ID from the IMAP server.
+
+## .inReplyTo
+
+**.inReplyTo** : string
+
+#### Description
+
+The `.inReplyTo` property contains the message identifier(s) of the original message(s) to which the current message is a reply.
+
+For specific formatting requirements, please consult the [RFC#5322](https://tools.ietf.org/html/rfc5322).
+
+## .keywords
+
+**.keywords** : object
+
+#### Description
+
+The `.keywords` property contains a set of keywords as an object, where each property name is a keyword and each value is true.
+
+This property is the "keywords" header (see [RFC#4021](https://tools.ietf.org/html/rfc4021)).
+
+|Property|Type|Value|
+|---|---|---|
+|.*keyword*|boolean|Keyword to set (value must be true)|
+
+Reserved keywords:
+- draft - Indicates a message is a draft
+- seen - Indicates a message has been read
+- flagged - Indicates a message needs special attention (e.g., Urgent)
+- answered - Indicates a message has been replied to
+- deleted - Indicates a message to delete
+
+#### Example
+
+```qs
+ mail.keywords["flagged"] = True
+ mail.keywords["qodly"] = True
+```
+
+## .messageId
+
+**.messageId** : string
+
+#### Description
+
+The `.messageId` property contains a message identifier header ("message-id").
+
+This header is usually "lettersOrNumbers@domainname", e.g. "abcdef.123456@4d.com". This unique ID is used in particular on forums or public mailing lists. In general, mail servers automatically add this header to the messages they send.
+
+## .receivedAt
+
+**.receivedAt** : string
+
+#### Description
+
+[IMAP transporter](IMAPTransporterClass.md) only.
+
+The `.receivedAt` property contains the timestamp of the email's arrival on the IMAP server in ISO 8601 UTC format (ex: 2020-09-13T16:11:53Z).
+
+## .references
+
+**.references** : collection
+
+#### Description
+
+The `.references` property contains the collection of all message-ids of messages in the preceding reply chain.
+
+For specific formatting requirements, please consult the [RFC#5322](https://tools.ietf.org/html/rfc5322).
+
+## .replyTo
+
+**.replyTo** : string **.replyTo** : object **.replyTo** : collection
+
+#### Description
+
+The `.replyTo` property contains the [addresse(s)](#email-addresses) for responses.
+
+## .sendAt
+
+**.sendAt** : string
+
+#### Description
+
+The `.sendAt` property contains the Email timestamp in ISO 8601 UTC format.
+
+## .sender
+
+**.sender** : string **.sender** : object **.sender** : collection
+
+#### Description
+
+The `.sender` property contains the email source [addresse(s)](#email-addresses) of the email.
+
+Each email you send out has both the **sender** and **[from](#from)** addresses:
+
+- the sender domain is what the receiving email server gets when opening the session,
+- the from address is what the recipient(s) will see.
+
+For better deliverability, it is recommended to use the same from and sender addresses.
+
+## .size
+
+**.size** : integer
+
+#### Description
+
+[IMAP transporter](IMAPTransporterClass.md) only.
+
+The `.size` property contains the size (expressed in bytes) of the Email object returned by the IMAP server.
+
+## .subject
+
+**.subject** : string
+
+#### Description
+
+The `.subject` property contains the description of topic.
+
+## .stringBody
+
+**.stringBody** : string
+
+#### Description
+
+The `.stringBody` property contains the Plain string representation of the email message (default charset is UTF-8) (optional, SMTP only). See [Handling body part](#handling-body-part) section.
+
+## .to
+
+**.to** : string **.to** : object **.to** : collection
+
+#### Description
+
+The `.to` property contains the primary recipient [addresse(s)](#email-addresses) of the email.
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/EndpointsClass.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/EndpointsClass.md
new file mode 100644
index 000000000..b0e1a5c01
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/EndpointsClass.md
@@ -0,0 +1,167 @@
+---
+id: EndpointsClass
+title: Endpoints
+---
+
+
+## Overview
+
+The **Endpoints** class provides access to both public and authenticated system endpoints, including custom domain endpoints. It simplifies retrieving the necessary endpoint types for secure and public access management across different environments, such as development.
+
+:::tip
+The **Endpoints** class is a **shared singleton**, meaning a single instance is globally available without needing manual creation. You can access it directly via the [.me](#me) property without instantiating a new instance.
+:::
+
+
+## Functions and properties
+
+||
+|---|
+|[](#authenticatedendpoint) |
+|[](#customdomainauthenticatedendpoint) |
+|[](#customdomainpublicendpoint) |
+|[](#me) |
+|[](#publicendpoint) |
+
+
+
+
+## .authenticatedEndpoint()
+
+**.authenticatedEndpoint**() : String
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|Result|String|←|Authenticated endpoint URL for the current environment.|
+
+
+#### Description
+
+The `.authenticatedEndpoint()` function returns the authenticated API endpoint URL specific to the current Qodly environment.
+
+#### Example
+
+To implement a method that retrieves the authenticated endpoint in a custom class:
+
+```qs
+var authEndpoint : string
+authEndpoint = cs.Qodly.Endpoints.me.authenticatedEndpoint()
+```
+
+
+
+## .customDomainAuthenticatedEndpoint()
+
+**.customDomainAuthenticatedEndpoint**() : String
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|Result|String|←|Authenticated endpoint URL for a custom domain or null.|
+
+
+#### Description
+
+The `.customDomainAuthenticatedEndpoint()` function returns the authenticated API endpoint URL for a custom domain if it has been configured.
+
+#### Example
+
+To implement a method that retrieves the custom domain authenticated endpoint in a custom class:
+
+```qs
+var customAuthEndpoint : string
+customAuthEndpoint = cs.Qodly.Endpoints.me.customDomainAuthenticatedEndpoint()
+```
+
+
+## .customDomainPublicEndpoint()
+
+**.customDomainPublicEndpoint**() : String
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|Result|String|←|Public endpoint URL for a custom domain or null.|
+
+
+#### Description
+
+The `.customDomainPublicEndpoint()` function returns the public endpoint URL for a custom domain if it has been configured in the Qodly application.
+
+#### Example
+
+To implement a method that retrieves the custom domain public endpoint in a custom class:
+
+```qs
+var customPublicEndpoint : string
+customPublicEndpoint = cs.Qodly.Endpoints.me.customDomainPublicEndpoint()
+```
+
+
+
+## .me
+
+**.me** : cs.Qodly.Endpoints
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|Result|cs.Qodly.Endpoints|←|The Endpoints Singleton Object containing system endpoints.|
+
+
+#### Description
+
+The `.me` property returns the current instance of the Endpoints singleton. It provides direct access to the system endpoints.
+
+It must be used when calling functions like [publicEndpoint()](#) or [`authenticatedEndpoint()`](#).
+
+:::tip Why .me is Required:
+If you attempt to call:
+
+```qs
+var publicEndpoint : string
+publicEndpoint = cs.Qodly.Endpoints.publicEndpoint()
+```
+
+You will encounter an error: `function call error (is not a function)`.
+
+The correct way is:
+
+```qs
+var publicEndpoint : string
+publicEndpoint = cs.Qodly.Endpoints.me.publicEndpoint()
+```
+
+The `.me` ensures the function is being called from the current active instance of the Endpoints singleton.
+:::
+
+
+
+## .publicEndpoint()
+
+**.publicEndpoint**() : String
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|Result|String|←|Public endpoint URL for the current Qodly environment.|
+
+
+#### Description
+
+The `.publicEndpoint()` function returns the public API endpoint URL for the current Qodly environment, allowing external services to access public resources without authentication.
+
+#### Example
+
+To implement a method that retrieves the custom domain authenticated endpoint in a custom class:
+
+```qs
+var publicEndpoint : string
+publicEndpoint = cs.Qodly.Endpoints.me.publicEndpoint()
+```
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/EntityClass.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/EntityClass.md
new file mode 100644
index 000000000..824e76f98
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/EntityClass.md
@@ -0,0 +1,1642 @@
+---
+id: EntityClass
+title: Entity
+---
+
+An [entity](../qodlyScript/guides/data-model.md#entity) is an instance of a [Dataclass](../qodlyScript/guides/data-model.md#dataclass), like a record of the table matching the dataclass in its associated datastore. It contains the same attributes as the dataclass as well as the data values and specific properties and functions.
+
+
+### Functions and properties
+
+||
+|---|
+|[](#attributename) |
+|[](#clone) |
+|[](#diff) |
+|[](#drop) |
+|[](#first) |
+|[](#fromobject) |
+|[](#getdataclass) |
+|[](#getkey) |
+|[](#getselection) |
+|[](#getstamp) |
+|[](#indexof) |
+|[](#isnew) |
+|[](#last) |
+|[](#lock) |
+|[](#next) |
+|[](#previous) |
+|[](#reload) |
+|[](#save) |
+|[](#toobject) |
+|[](#touched) |
+|[](#touchedattributes-) |
+|[](#unlock) |
+
+
+
+
+
+
+
+## .*attributeName*
+
+
+
+***.attributeName*** : any
+
+
+#### Description
+
+Any dataclass attribute is available as a property of an entity, which stores the attribute value for the entity.
+
+:::note
+
+Dataclass attributes can also be reached using the alternate syntax with `[]`.
+
+:::
+
+The attribute value type depends on the attribute [kind](DataClassClass.md#attributename):
+
+* If *attributeName* kind is **storage**:
+`.attributeName` returns a value of the same type as *attributeName*.
+* If *attributeName* kind is **relatedEntity**:
+`.attributeName` returns the related entity. Values of the related entity are directly available through cascading properties, for example "myEntity.employer.employees\[0].lastname".
+* If *attributeName* kind is **relatedEntities**:
+`.attributeName` returns a new entity selection of related entities. Duplications are removed (an unordered entity selection is returned).
+* If *attributeName* kind is **calculated**:
+`.attributeName` can return any type of value, depending on the `get` function declaration.
+
+#### Example
+
+```qs
+ var myEntity : cs.EmployeeEntity
+ myEntity = ds.Employee.new() //Create a new entity
+ myEntity.name = "Dupont" // assign 'Dupont' to the 'name' attribute
+ myEntity.firstname = "John" //assign 'John' to the 'firstname' attribute
+ myEntity.save() //save the entity
+```
+
+
+
+
+
+
+
+## .clone()
+
+
+
+**.clone**() : 4D.Entity
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.Entity|←|New entity referencing the record|
+
+
+
+#### Description
+
+The `.clone()` function creates in memory a new entity referencing the same record as the original entity.
+
+This function allows you to update entities separately. Note however that, for performance reasons, the new entity shares the same reference of object attributes as the cloned entity.
+
+>Keep in mind that any modifications done to entities will be saved in the referenced record only when the [`.save()`](#save) function is executed.
+
+This function can only be used with entities already saved in the database. It cannot be called on a newly created entity (for which [`.isNew()`](#isnew) returns **true**).
+
+
+#### Example
+
+```qs
+ var emp, empCloned : cs.EmployeeEntity
+ emp = ds.Employee.get(672)
+ empCloned = emp.clone()
+
+ emp.lastName = "Smith" //Updates done on emp are not done on empCloned
+
+
+```
+
+
+
+
+
+
+
+
+## .diff()
+
+
+
+**.diff**( *entityToCompare* : 4D.Entity \{ , *attributesToCompare* : collection \} ) : collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|entityToCompare|4D.Entity|→|Entity to be compared with the original entity|
+|attributesToCompare|collection|→ |Name of attributes to be compared |
+|Result|collection|←|Differences between the entities|
+
+
+
+#### Description
+
+The `.diff()` function compares the contents of two entities and returns their differences.
+
+In *entityToCompare*, pass the entity to be compared to the original entity.
+
+In *attributesToCompare*, you can designate specific attributes to compare. If provided, the comparison is done only on the specified attributes. If not provided, all differences between the entities are returned.
+
+The differences are returned as a collection of objects whose properties are:
+
+|Property name| Type| Description|
+|---|---|---|
+|attributeName| string| Name of the attribute
+|value|any - Depends on attribute type |Value of the attribute in the entity|
+|otherValue|any - Depends on attribute type|Value of the attribute in *entityToCompare*|
+
+Only attributes with different values are included in the collection. If no differences are found, `.diff()` returns an empty collection.
+
+The function applies for properties whose [kind](DataClassClass.md#attributename) is **storage** or **relatedEntity**. In case a related entity has been updated (meaning the foreign key), the name of the related entity and its primary key name are returned as *attributeName* properties (*value* and *otherValue* are empty for the related entity name).
+
+If one of the compared entities is **Null**, an error is raised.
+
+#### Example 1
+
+
+```qs
+ var diff1, diff2 : collection
+ var employee, clone : cs.EmployeeEntity
+ employee = ds.Employee.query("ID = 1001").first()
+ clone = employee.clone()
+ employee.firstName = "MARIE"
+ employee.lastName = "SOPHIE"
+ employee.salary = 500
+ diff1 = clone.diff(employee) // All differences are returned
+ diff2 = clone.diff(employee,newCollection("firstName","lastName"))
+ // Only differences on firstName and lastName are returned
+```
+
+diff1:
+
+```qs
+[
+ {
+ "attributeName": "firstName",
+ "value": "Natasha",
+ "otherValue": "MARIE"
+ },
+ {
+ "attributeName": "lastName",
+ "value": "Locke",
+ "otherValue": "SOPHIE"
+ },
+ {
+ "attributeName": "salary",
+ "value": 66600,
+ "otherValue": 500
+ }
+]
+diff2:
+
+[
+ {
+ "attributeName": "firstName",
+ "value": "Natasha",
+ "otherValue": "MARIE"
+ },
+ {
+ "attributeName": "lastName",
+ "value": "Locke",
+ "otherValue": "SOPHIE"
+ }
+]
+```
+
+#### Example 2
+
+```qs
+ var vCompareResult1, vCompareResult2, vCompareResult3, attributesToInspect : collection
+ var e1, e2 : cs.EmployeeEntity
+ var c : cs.CompanyEntity
+ vCompareResult1 = newCollection
+ vCompareResult2 = newCollection
+ vCompareResult3 = newCollection
+ attributesToInspect: = newCollection
+
+ e1 = ds.Employee.get(636)
+ e2 = ds.Employee.get(636)
+
+ e1.firstName = e1.firstName+" update"
+ e1.lastName = e1.lastName+" update"
+
+ c = ds.Company.get(117)
+ e1.employer = c
+ e2.salary = 100
+
+ attributesToInspect.push("firstName")
+ attributesToInspect.push("lastName")
+
+ vCompareResult1 = e1.diff(e2)
+ vCompareResult2 = e1.diff(e2,attributesToInspect)
+ vCompareResult3 = e1.diff(e2,e1.touchedAttributes())
+```
+
+vCompareResult1 (all differences are returned):
+
+```qs
+[
+ {
+ "attributeName": "firstName",
+ "value": "Karla update",
+ "otherValue": "Karla"
+ },
+ {
+ "attributeName": "lastName",
+ "value": "Marrero update",
+ "otherValue": "Marrero"
+ },
+ {
+ "attributeName": "salary",
+ "value": 33500,
+ "otherValue": 100
+ },
+ {
+ "attributeName": "employerID",
+ "value": 117,
+ "otherValue": 118
+ },
+ {
+ "attributeName": "employer",
+ "value": "[object Entity]",// Entity 117 from Company
+ "otherValue": "[object Entity]"// Entity 118 from Company
+ }
+]
+```
+
+vCompareResult2 (only differences on attributesToInspect are returned)
+
+```qs
+[
+ {
+ "attributeName": "firstName",
+ "value": "Karla update",
+ "otherValue": "Karla"
+ },
+ {
+ "attributeName": "lastName",
+ "value": "Marrero update",
+ "otherValue": "Marrero"
+ }
+]
+```
+
+vCompareResult3 (only differences on e1 touched attributes are returned)
+
+```qs
+[
+ {
+ "attributeName": "firstName",
+ "value": "Karla update",
+ "otherValue": "Karla"
+ },
+ {
+ "attributeName": "lastName",
+ "value": "Marrero update",
+ "otherValue": "Marrero"
+ },
+ {
+ "attributeName": "employerID",
+ "value": 117,
+ "otherValue": 118
+ },
+ {
+ "attributeName": "employer",
+ "value": "[object Entity]",// Entity 117 from Company
+ "otherValue": "[object Entity]"// Entity 118 from Company
+
+ }
+]
+```
+
+
+
+
+
+
+
+## .drop()
+
+
+**.drop**( \{*mode* : integer\} ) : object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|mode|integer|→|`kForceDropIfStampChanged`: Forces the drop even if the stamp has changed|
+|Result|object|←|Result of drop operation|
+
+
+#### Description
+
+The `.drop()` function deletes the data contained in the entity from the datastore, from the table related to its Dataclass. Note that the entity remains in memory.
+
+In a multi-process application, the `.drop()` function is executed under an ["optimistic lock"](../qodlyScript/guides/data.md#entity-locking) mechanism, wherein an internal locking stamp is automatically incremented each time the record is saved.
+
+By default, if the *mode* parameter is omitted, the function will return an error (see below) if the same entity was modified (i.e. the stamp has changed) by another process in the meantime.
+
+Otherwise, you can pass the `kForceDropIfStampChanged` option in the *mode* parameter: in this case, the entity is dropped even if the stamp has changed (and the primary key is still the same).
+
+**Result**
+
+The object returned by `.drop( )` contains the following properties:
+
+|Property| | Type |Description|
+|---|---|--- |---|
+|success|| boolean|true if the drop action is successful, false otherwise.|
+||||***Available only in case of error:***|
+|status(*) || number| Error code, see below|
+|statusText(*)|| string| Description of the error, see below|
+||||***Available only in case of pessimistic lock error:***|
+|LockKindText|| string| "Locked by record"|
+|lockInfo|| object| Information about the lock origin|
+||task_id| number| Process id|
+||user_name| string| Session user name on the machine|
+||user4d_alias| string| *unused*|
+||host_name |string| Machine name|
+||task_name| string| Process name|
+||client_version |string|
+||||***Available only in case of serious error (serious error can be trying to duplicate a primary key, disk full...):***|
+|errors || collection of objects| |
+||message| string| Error message|
+||component signature| string| internal component signature (e.g. "dmbg" stands for the database component)|
+||errCode |number |Error code|
+
+(\*) The following values can be returned in the *status* and *statusText* properties of *Result* object in case of error:
+
+|Constant| Value| Comment|
+|---|---|---|
+|`kStatusEntityDoesNotExistAnymore`|5|The entity no longer exists in the data. This error can occur in the following cases:
the entity has been dropped (the stamp has changed and the memory space is now free)
the entity has been dropped and replaced by another one with another primary key (the stamp has changed and a new entity now uses the memory space). This error can be returned when `kForceDropIfStampChanged` option is used.
**Associated statusText**: "Entity does not exist anymore"|
+|`kStatusLocked`|3|The entity is locked by a pessimistic lock. **Associated statusText**: "Already locked"|
+|`kStatusSeriousError`| 4| A serious error is a low-level database error (e.g. duplicated key), a hardware error, etc. **Associated statusText**: "Other error"|
+|`kStatusStampHasChanged`| 2|The internal stamp value of the entity does not match the one of the entity stored in the data (optimistic lock). This error can only occur if the `kForceDropIfStampChanged` option is not used **Associated statusText**: "Stamp has changed"|
+
+
+#### Example 1
+
+Example without `kForceDropIfStampChanged` option:
+
+```qs
+ var employees : cs.EmployeeSelection
+ var employee : cs.EmployeeEntity
+ var status : object
+ var info : string
+ employees = ds.Employee.query("lastName = :1","Smith")
+ employee = employees.first()
+ status = employee.drop()
+ switch
+ :(status.success)
+ info = "You have dropped "+employee.firstName+" "+employee.lastName) //The dropped entity remains in memory
+ :(status.status = kStatusStampHasChanged)
+ info = status.statusText
+ end
+```
+
+#### Example 2
+
+Example with `kForceDropIfStampChanged` option:
+
+```qs
+ var employees : cs.EmployeeSelection
+ var employee : cs.EmployeeEntity
+ var status : object
+ var info : string
+ employees = ds.Employee.query("lastName = :1","Smith")
+ employee = employees.first()
+ status = employee.drop(kForceDropIfStampChanged)
+ switch
+ :(status.success)
+ info = "You have dropped "+employee.firstName+" "+employee.lastName) //The dropped entity remains in memory
+ :(status.status = kStatusEntityDoesNotExistAnymore)
+ info = status.statusText
+ end
+```
+
+
+
+
+
+
+
+## .first()
+
+
+
+**.first**(): 4D.Entity
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.Entity|←|Reference to first entity of an entity selection (null if not found)|
+
+
+#### Description
+
+The `.first()` function returns a reference to the entity in first position of the entity selection which the entity belongs to.
+
+If the entity does not belong to any existing entity selection (i.e. [.getSelection( )](#getselection) returns null), the function returns a null value.
+
+#### Example
+
+```qs
+ var employees : cs.EmployeeSelection
+ var employee, firstEmployee : cs.EmployeeEntity
+ employees = ds.Employee.query("lastName = :1","H@") //This entity selection contains 3 entities
+ employee = employees[2]
+ firstEmployee = employee.first() //firstEmployee is the first entity of the employees entity selection
+```
+
+
+
+
+
+
+## .fromObject()
+
+
+**.fromObject**( *filler* : object )
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|filler|object|→|object from which to fill the entity|
+
+
+#### Description
+
+The `.fromObject()` function fills an entity with the *filler* content.
+
+>This function modifies the original entity.
+
+The mapping between the object and the entity is done on the attribute names:
+
+* If a property of the object does not exist in the dataclass, it is ignored.
+* Data types must be equivalent. If there is a type mismatch between the object and dataclass, ORDA tries to convert the data whenever possible, otherwise the attribute is left untouched.
+* The primary key can be given as is or with a "__KEY" property (filled with the primary key value). If it does not already exist in the dataclass, the entity is created with the given value when [.save()](#save) is called. If the primary key is not given, the entity is created and the primary key value is assigned with respect to database rules. The auto-increment is only computed if the primary key is null.
+
+*filler* can handle a related entity under the following conditions:
+
+* *filler* contains the foreign key itself, or
+* *filler* contains a property object with the same name as the related entity, containing a single property named "\_\_KEY".
+* if the related entity does not exist, it is ignored.
+
+#### Example
+
+With the following *o* object:
+
+```qs
+{
+ "firstName": "Mary",
+ "lastName": "Smith",
+ "salary": 36500,
+ "birthDate": "1958-10-27T00:00:00.000Z",
+ "woman": true,
+ "managerID": 411,// relatedEntity given with PK
+ "employerID": 20 // relatedEntity given with PK
+}
+```
+
+
+The following code will create an entity with manager and employer related entities.
+
+
+```qs
+ var o : object
+ var entity : cs.EmpEntity
+
+ entity = ds.Emp.new()
+ entity.fromObject(o)
+ entity.save()
+```
+
+
+You could also use a related entity given as an object:
+
+```qs
+
+{
+ "firstName": "Marie",
+ "lastName": "Lechat",
+ "salary": 68400,
+ "birthDate": "1971-09-03T00:00:00.000Z",
+ "woman": false,
+ "employer": {// relatedEntity given as an object
+ "__KEY": "21"
+ },
+ "manager": {// relatedEntity given as an object
+ "__KEY": "411"
+ }
+}
+```
+
+
+
+
+
+
+
+
+## .getDataClass()
+
+
+
+**.getDataClass()** : 4D.DataClass
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.DataClass|←|DataClass object to which the entity belongs|
+
+
+#### Description
+
+The `.getDataClass()` function returns the dataclass of the entity. This function is useful when writing generic code.
+
+
+#### Example
+
+The following generic code duplicates any entity:
+
+```qs
+ //duplicate_entity method
+ //duplicate_entity(entity)
+
+ declare (entity : 4D.Entity)
+ var entityNew : 4D.Entity
+ var status : object
+
+ entityNew = entity.getDataClass().new() //create a new entity in the parent dataclass
+ entityNew.fromObject(entity.toObject()) //get all attributes
+ entityNew[entity.getDataClass().getInfo().primaryKey] = Null //reset the primary key
+ status = entityNew.save() //save the duplicated entity
+```
+
+
+
+
+
+
+
+## .getKey()
+
+
+
+**.getKey**( \{ *mode* : integer \} ) : string **.getKey**( \{ *mode* : integer \} ) : integer
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|mode|integer|→|`kKeyAsString`: primary key is returned as a string, no matter the primary key type|
+|Result|string|←|Value of the string primary key of the entity|
+|Result|integer|←|Value of the numeric primary key of the entity|
+
+
+
+#### Description
+
+The `.getKey()` function returns the primary key value of the entity.
+
+Primary keys can be numbers (integer) or strings. You can "force" the returned primary key value to be a string, no matter the actual primary key type, by passing the `kKeyAsString` option in the *mode* parameter.
+
+#### Example
+
+
+```qs
+ var employees : cs.EmployeeSelection
+ var employee : cs.EmployeeEntity
+ var info : string
+ employees = ds.Employee.query("lastName = :1","Smith")
+ employee = employees[0]
+ info = "The primary key is "+employee.getKey(kKeyAsString)
+```
+
+
+
+
+
+
+
+## .getSelection()
+
+
+**.getSelection()**: 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.EntitySelection|←|Entity selection to which the entity belongs (null if not found)|
+
+
+#### Description
+
+The `.getSelection()` function returns the entity selection which the entity belongs to.
+
+If the entity does not belong to an entity selection, the function returns null.
+
+#### Example
+
+
+```qs
+ var emp : cs.EmployeeEntity
+ var employees, employees2 : cs.EmployeeSelection
+ var info : string
+ emp = ds.Employee.get(672) // This entity does not belong to any entity selection
+ employees = emp.getSelection() // employees is Null
+
+ employees2 = ds.Employee.query("lastName = :1","Smith") //This entity selection contains 6 entities
+ emp = employees2[0] //This entity belongs to an entity selection
+
+ info = "The entity selection contains "+string(emp.getSelection().length)+" entities"
+```
+
+
+
+
+
+
+## .getStamp()
+
+
+
+**.getStamp**() : integer
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|integer|←|Stamp of the entity (0 if entity has just been created)|
+
+
+#### Description
+
+The `.getStamp()` function returns the current value of the stamp of the entity.
+
+The internal stamp is automatically incremented by the database each time the entity is saved. It manages concurrent user access and modifications to the same entities (see [**Entity locking**](../qodlyScript/guides/data.md#entity-locking)).
+
+:::note
+
+For a new entity (never saved), the function returns 0. To know if an entity has just been created, it is recommended to use [.isNew()](#isnew).
+
+:::
+
+#### Example
+
+
+```qs
+ var entity : cs.EmployeeEntity
+ var stamp : integer
+
+ entity = ds.Employee.new()
+ entity.lastname = "Smith"
+ entity.save()
+ stamp = entity.getStamp() //stamp = 1
+
+ entity.lastname = "Wesson"
+ entity.save()
+ stamp = entity.getStamp() //stamp = 2
+```
+
+
+
+
+
+
+
+## .indexOf()
+
+
+
+**.indexOf**( \{ *entitySelection* : 4D.EntitySelection \} ) : integer
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|entitySelection|4D.EntitySelection|→|Position of the entity is given according to this entity selection|
+|Result|integer|←|Position of the entity in an entity selection|
+
+
+#### Description
+
+The `.indexOf()` function returns the position of the entity in an entity selection.
+
+By default if the *entitySelection* parameter is omitted, the function returns the entity's position within its own entity selection. Otherwise, it returns the position of the entity within the specified *entitySelection*.
+
+The resulting value is included between 0 and the length of the entity selection -1.
+
+* If the entity does not have an entity selection or does not belong to *entitySelection*, the function returns -1.
+* If *entitySelection* is Null or does not belong to the same dataclass as the entity, an error is raised.
+
+#### Example
+
+
+```qs
+ var employees : cs.EmployeeSelection
+ var employee : cs.EmployeeEntity
+ var info : string
+ employees = ds.Employee.query("lastName = :1","H@") //This entity selection contains 3 entities
+ employee = employees[1] //This entity belongs to an entity selection
+ info = "The index of the entity in its own entity selection is "+string(employee.indexOf()) //1
+
+ employee = ds.Employee.get(725) //This entity does not belong to an entity selection
+ info = "The index of the entity is "+string(employee.indexOf()) // -1
+```
+
+
+
+
+
+
+
+## .isNew()
+
+
+
+**.isNew**() : boolean
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|boolean|←|True if entity has just been created and not yet saved. Otherwise, false.|
+
+
+#### Description
+
+The `.isNew()` function returns true if the entity to which it is applied has just been created and has not yet been saved in the datastore. Otherwise, it returns false.
+
+
+#### Example
+
+
+```qs
+ var emp : cs.EmployeeEntity
+ var info : string
+
+ emp = ds.Employee.new()
+
+ if(emp.isNew())
+ info = "This is a new entity"
+ end
+```
+
+
+
+
+
+
+## .last()
+
+
+**.last**() : 4D.Entity
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.Entity|←|Reference to last entity of an entity selection (null if not found)|
+
+
+#### Description
+
+The `.last()` function returns a reference to the entity in last position of the entity selection which the entity belongs to.
+
+If the entity does not belong to any existing entity selection (i.e. [.getSelection()](#getselection) returns null), the function returns a null value.
+
+
+#### Example
+
+
+```qs
+ var employees : cs.EmployeeSelection
+ var employee, lastEmployee : cs.EmployeeEntity
+ employees = ds.Employee.query("lastName = :1","H@") //This entity selection contains 3 entities
+ employee = employees[0]
+ lastEmployee = employee.last() //lastEmployee is the last entity of the employees entity selection
+```
+
+
+
+
+
+
+## .lock()
+
+
+
+**.lock**( \{ *mode* : integer \} ) : object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|mode|integer|→|`kReloadIfStampChanged`: Reload before locking if stamp changed|
+|Result|object|←|Result of lock operation|
+
+
+#### Description
+
+The `.lock()` function puts a pessimistic lock on the record referenced by the entity. The [lock is set](../qodlyScript/guides/data.md#entity-locking) for a record and all the references of the entity in the current process.
+
+Other processes will see this record as locked (the `result.success` property will contain False if they try to lock the same entity using this function). Only functions executed in the "locking" session are allowed to edit and save the attributes of the entity. The entity can be loaded as read-only by other sessions, but they will not be able to enter and save values.
+
+A locked record is unlocked:
+
+* when the [`unlock()`](#unlock) function is called on a matching entity in the same process
+* automatically, when it is no longer referenced by any entities in memory. For example, if the lock is put only on one local reference of an entity, the entity is unlocked when the function ends. As long as there are references to the entity in memory, the record remains locked.
+
+By default, if the *mode* parameter is omitted, the function will return an error (see below) if the same entity was modified (i.e. the stamp has changed) by another process or user in the meantime.
+
+Otherwise, you can pass the `kReloadIfStampChanged` option in the *mode* parameter: in this case, no error is returned and the entity is reloaded when the stamp has changed (if the entity still exists and the primary key is still the same).
+
+**Result**
+
+The object returned by `.lock()` contains the following properties:
+
+|Property| | Type| Description|
+|---|---|---|---|
+|success| | boolean| true if the lock action is successful (or if the entity is already locked in the current process), false otherwise.|
+||||***Available only if `kReloadIfStampChanged` option is used:***|
+|**wasReloaded**| |boolean|true if the entity was reloaded with success, false otherwise.|
+||||***Available only in case of error:***|
+|status(\*)| |number| Error code, see below|
+|statusText(\*)|| string| Description of the error, see below|
+||||***Available only in case of pessimistic lock error:***|
+|lockKindText| | string| "Locked by record"|
+|lockInfo| | object| Information about the lock origin|
+||task_id| number| Process ID|
+||user_name |string| Session user name on the machine|
+||user4d_alias| string| *unused*|
+||user4d_id |number |User id in the database directory|
+||host_name| string| Machine name
+||task_name |string |Process name|
+||client_version| string ||
+||||***Available only in case of serious error*** (primary key already exists, disk full...):|
+|errors || collection of objects ||
+||message |string |Error message|
+||component signature| string| internal component signature (e.g. "dmbg" stands for the database component)|
+||errCode |number |Error code|
+
+
+(\*) The following values can be returned in the *status* and *statusText* properties of the *Result* object in case of error:
+
+|Constant |Value| Comment|
+|---|---|---|
+|`kStatusEntityDoesNotExistAnymore`| 5|The entity no longer exists in the data. This error can occur in the following cases:
the entity has been dropped (the stamp has changed and the memory space is now free)
the entity has been dropped and replaced by another one with another primary key (the stamp has changed and a new entity now uses the memory space). This error can only be returned when `kReloadIfStampChanged` option is used
**Associated statusText**: "Entity does not exist anymore"|
+|`kStatusLocked`| 3 |The entity is locked by a pessimistic lock. **Associated statusText**: "Already locked"
+|`kStatusSeriousError`| 4 |A serious error is a low-level database error (e.g. duplicated key), a hardware error, etc. **Associated statusText**: "Other error"|
+|`kStatusStampHasChanged`|2|The internal stamp value of the entity does not match the one of the entity stored in the data (optimistic lock). This error occurs only if the `kReloadIfStampChanged` option is not used **Associated statusText**: "Stamp has changed"|
+
+
+#### Example 1
+
+Example with error:
+
+```qs
+ var employee : cs.EmployeeEntity
+ var status : object
+ var info : string
+ employee = ds.Employee.get(716)
+ status = employee.lock()
+ switch
+ :(status.success)
+ info = "You have locked "+employee.firstName+" "+employee.lastName
+ :(status.status = kStatusStampHasChanged)
+ info = status.statusText
+ end
+```
+
+
+#### Example 2
+
+Example with `kReloadIfStampChanged` option:
+
+```qs
+ var employee : cs.EmployeeEntity
+ var status : object
+ var info : string
+ employee = ds.Employee.get(717)
+ status = employee.lock(kReloadIfStampChanged)
+ switch
+ :(status.success)
+ info = "You have locked "+employee.firstName+" "+employee.lastName
+ :(status.status = kStatusEntityDoesNotExistAnymore)
+ info = status.statusText
+ end
+```
+
+
+
+
+
+
+## .next()
+
+
+**.next()** : 4D.Entity
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.Entity|←|Reference to next entity in the entity selection (Null if not found)|
+
+
+#### Description
+
+The `.next()` function returns a reference to the next entity in the entity selection which the entity belongs to.
+
+If the entity does not belong to any existing entity selection (i.e. [.getSelection()](#getselection) returns Null), the function returns a Null value.
+
+If there is no valid next entity in the entity selection (i.e. you are on the last entity of the selection), the function returns Null. If the next entity has been dropped, the function returns the next valid entity (and eventually Null).
+
+
+#### Example
+
+```qs
+ var employees : cs.EmployeeSelection
+ var employee, nextEmployee : cs.EmployeeEntity
+ employees = ds.Employee.query("lastName = :1","H@") //This entity selection contains 3 entities
+ employee = employees[0]
+ nextEmployee = employee.next() //nextEmployee is the second entity of the employees entity selection
+
+```
+
+
+
+
+
+## .previous()
+
+
+**.previous()** : 4D.Entity
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.Entity|←|Reference to previous entity in the entity selection (null if not found)|
+
+
+#### Description
+
+The `.previous()` function returns a reference to the previous entity in the entity selection which the entity belongs to.
+
+If the entity does not belong to any existing entity selection (i.e. [.getSelection()](#getselection) returns null), the function returns a Null value.
+
+If there is no valid previous entity in the entity selection (i.e. you are on the first entity of the selection), the function returns null. If the previous entity has been dropped, the function returns the previous valid entity (and eventually null).
+
+
+#### Example
+
+```qs
+ var employees : cs.EmployeeSelection
+ var employee, previousEmployee : cs.EmployeeEntity
+ employees = ds.Employee.query("lastName = :1","H@") //This entity selection contains 3 entities
+ employee = employees[1]
+ previousEmployee = employee.previous() //previousEmployee is the first entity of the employees entity selection
+```
+
+
+
+
+
+
+## .reload()
+
+
+**.reload**() : object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|object|←|Status object|
+
+
+#### Description
+
+The `.reload()` function reloads the content of the entity in memory, according to information stored in the table related to the dataclass in the datastore. The reload is done only if the entity still exists with the same primary key.
+
+**Result**
+
+The object returned by `.reload()` contains the following properties:
+
+|Property |Type| Description|
+|---|---|---|
+|success|boolean| True if the reload action is successful, false otherwise.|
+|||***Available only in case of error:***|
+|status(\*)|number|Error code, see below|
+|statusText(\*)|string|Description of the error, see below|
+
+(\*) The following values can be returned in the *status* and *statusText* properties of *Result* object in case of error:
+
+|Constant| Value| Comment|
+|---|---|---|
+|`kStatusEntityDoesNotExistAnymore`|5|The entity no longer exists in the data. This error can occur in the following cases:
the entity has been dropped (the stamp has changed and the memory space is now free)
the entity has been dropped and replaced by another one with another primary key (the stamp has changed and a new entity now uses the memory space).
***Associated statusText***: "Entity does not exist anymore"|
+|`kStatusSeriousError`|4| A serious error is a low-level database error (e.g. duplicated key), a hardware error, etc. ***Associated statusText***: "Other error"|
+
+
+#### Example
+
+```qs
+ var employee : cs.EmployeeEntity
+ var employees : cs.EmployeeSelection
+ var result : object
+ var info : string
+
+ employees = ds.Employee.query("lastName = :1","Hollis")
+ employee = employees[0]
+ employee.firstName = "Mary"
+ result = employee.reload()
+ switch
+ :(result.success)
+ info = "Reload has been done"
+ :(result.status = kStatusEntityDoesNotExistAnymore)
+ info = "The entity has been dropped"
+ end
+```
+
+
+
+
+
+## .save()
+
+
+**.save**( \{ *mode* : integer \} ) : object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|mode|integer|→|`kAutoMerge`: Enables the automatic merge mode|
+|Result|object|←|Result of save operation|
+
+
+#### Description
+
+The `.save()` function saves the changes made to the entity in the table related to its dataClass. You must call this function after creating or modifying an entity if you want to save the changes made to it.
+
+The save operation is executed only if at least one entity attribute has been "touched" (see the [`.touched()`](#touched) and [`.touchedAttributes()`](#touchedattributes-) functions). Otherwise, the function does nothing (the trigger is not called).
+
+In a multi-process application, the `.save()` function is executed under an ["optimistic lock"](../qodlyScript/guides/data.md#entity-locking) mechanism, wherein an internal locking stamp is automatically incremented each time the record is saved.
+
+By default, if the *mode* parameter is omitted, the method will return an error (see below) whenever the same entity has been modified by another process in the meantime, no matter the modified attribute(s).
+
+Otherwise, you can pass the `kAutoMerge` option in the *mode* parameter: when the automatic merge mode is enabled, a modification done concurrently by another process/user on the same entity but on a different attribute will not result in an error. The resulting data saved in the entity will be the combination (the "merge") of all non-concurrent modifications (if modifications were applied to the same attribute, the save fails and an error is returned, even with the auto merge mode).
+
+>The automatic merge mode is not available for attributes of type Picture and object. Concurrent changes in these attributes will result in a `kStatusStampHasChanged` error.
+
+**Result**
+
+The object returned by `.save()` contains the following properties:
+
+|Property | |Type| Description|
+|---|---|---|---|
+|success| |boolean|True if the save action is successful, False otherwise.|
+||||***Available only if `kAutoMerge` option is used***:|
+|autoMerged| |boolean|True if an auto merge was done, False otherwise.|
+||||***Available only in case of error***:|
+|status| |number|Error code, [see below](#status-and-statustext)|
+|statusText| |string|Description of the error, [see below](#status-and-statustext)|
+||||***Available only in case of pessimistic lock error***:|
+|lockKindText| |string|"Locked by record"|
+|lockInfo| |object|Information about the lock origin|
+||task_id| number| Process id|
+||user_name |string| Session user name on the machine|
+||user4d_alias| string| *unused*|
+||host_name |string |Machine name|
+||task_name| string| Process name|
+||client_version| string||
+||||***Available only in case of serious error*** (serious error - can be trying to duplicate a primary key, disk full...):|
+|errors || collection of objects||
+||message| string |Error message|
+||componentSignature| string| Internal component signature (e.g. "dmbg" stands for the database component)|
+||errCode| number| Error code|
+
+##### status and statusText
+
+The following values can be returned in the `status` and `statusText` properties of Result object in case of error:
+
+|Constant| Value |Comment|
+|---|---|---|
+|`kStatusAutomergeFailed`| 6| (Only if the `kAutoMerge` option is used) The automatic merge option failed when saving the entity. **Associated statusText**: "Auto merge failed"|
+|`kStatusEntityDoesNotExistAnymore`| 5| The entity no longer exists in the data. This error can occur in the following cases:
the entity has been dropped (the stamp has changed and the memory space is now free)
the entity has been dropped and replaced by another one with another primary key (the stamp has changed and a new entity now uses the memory space).
**Associated statusText**: "Entity does not exist anymore"|
+|`kStatusLocked`| 3| The entity is locked by a pessimistic lock. **Associated statusText**: "Already locked"
+|`kStatusSeriousError`|4|A serious error is a low-level database error (e.g. duplicated key), a hardware error, etc. **Associated statusText**: "Other error"|
+|`kStatusStampHasChanged`|2|The internal stamp value of the entity does not match the one of the entity stored in the data (optimistic lock). This error can only occur if the `kAutoMerge` option is not used **Associated statusText**: "Stamp has changed"|
+
+
+#### Example 1
+
+Creating a new entity:
+
+```qs
+ var status : object
+ var employee : cs.EmployeeEntity
+ var info : string
+ employee = ds.Employee.new()
+ employee.firstName = "Mary"
+ employee.lastName = "Smith"
+ status = employee.save()
+ if(status.success)
+ info = "Employee created"
+ end
+```
+
+#### Example 2
+
+Updating an entity without `kAutoMerge` option:
+
+```qs
+ var status : object
+ var employee : cs.EmployeeEntity
+ var employees : cs.EmployeeSelection
+ employees = ds.Employee.query("lastName = :1","Smith")
+ employee = employees.first()
+ employee.lastName = "Mac Arthur"
+ status = employee.save()
+ switch
+ :(status.success)
+ info = "Employee updated"
+ :(status.status = kStatusStampHasChanged)
+ info = status.statusText
+ end
+```
+
+#### Example 3
+
+Updating an entity with `kAutoMerge` option:
+
+```qs
+ var status : object
+
+ var employee : cs.EmployeeEntity
+ var employees : cs.EmployeeSelection
+ var info : string
+
+ employees = ds.Employee.query("lastName = :1","Smith")
+ employee = employees.first()
+ employee.lastName = "Mac Arthur"
+ status = employee.save(kAutoMerge)
+ switch
+ :(status.success)
+ info = "Employee updated"
+ :(status.status = kStatusAutomergeFailed)
+ info = status.statusText
+ end
+```
+
+
+
+
+
+
+## .toObject()
+
+
+
+**.toObject**() : object **.toObject**( *filterString* : string \{ , *options* : integer} ) : object **.toObject**( *filterCol* : collection \{ , *options* : integer \} ) : object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|filterString |string |→|Attribute(s) to extract (comma-separated string)|
+|filterCol |collection |→|collection of attribute(s) to extract|
+|options|integer|→|`kWithPrimaryKey`: adds the \_KEY property; `kWithStamp`: adds the \_STAMP property|
+|Result|object|←|object built from the entity|
+
+
+#### Description
+
+The `.toObject()` function returns an object which has been built from the entity. Property names in the object match attribute names of the entity.
+
+If no filter is specified, or if the *filterString* parameter contains an empty string or "*", the returned object will contain:
+
+* all storage entity attributes
+* attributes of the `relatedEntity` [kind](DataClassClass.md#attributename): you get a property with the same name as the related entity (name of the many-to-one link). Attribute is extracted with the simple form.
+* attributes of the `relatedEntities` [kind](DataClassClass.md#attributename): attribute is not returned.
+
+
+In the first parameter, you pass the entity attribute(s) to extract. You can pass:
+
+* *filterString*: a string with property paths separated with commas: "propertyPath1, propertyPath2, ...", or
+* *filterCol*: a collection of strings: \["propertyPath1","propertyPath2";...]
+
+If a filter is specified for attributes of the relatedEntity [kind](DataClassClass.md#attributename):
+
+* propertyPath = "relatedEntity" -> it is extracted with simple form: an object with property \_\_KEY (primary key).
+* propertyPath = "relatedEntity.*" -> all the properties are extracted
+* propertyPath = "relatedEntity.propertyName1; relatedEntity.propertyName2; ..." -> only those properties are extracted
+
+If a filter is specified for attributes of the relatedEntities [kind](DataClassClass.md#attributename):
+
+* propertyPath = "relatedEntities.*" -> all the properties are extracted
+* propertyPath = "relatedEntities.propertyName1; relatedEntities.propertyName2; ..." -> only those properties are extracted
+
+In the *options* parameter, you can pass the `kWithPrimaryKey` and/or` kWithStamp` selector(s) to add the entity's primary keys and/or stamps in extracted objects.
+
+
+#### Example 1
+
+The following structure will be used throughout all examples of this section:
+
+
+
+
+Without filter parameter:
+
+```qs
+employeeObject = employeeSelected.toObject()
+```
+
+Returns:
+
+```json
+{
+ "ID": 413,
+ "firstName": "Greg",
+ "lastName": "Wahl",
+ "salary": 0,
+ "birthDate": "1963-02-01T00:00:00.000Z",
+ "woman": false,
+ "managerID": 412,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": { // relatedEntity extracted with simple form
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 412
+ }
+}
+```
+
+
+
+#### Example 2
+
+Extracting the primary key and the stamp:
+
+```qs
+employeeObject = employeeSelected.toObject("",kWithPrimaryKey+kWithStamp)
+```
+
+Returns:
+
+```json
+{
+ "__KEY": 413,
+ "__STAMP": 1,
+ "ID": 413,
+ "firstName": "Greg",
+ "lastName": "Wahl",
+ "salary": 0,
+ "birthDate": "1963-02-01T00:00:00.000Z",
+ "woman": false,
+ "managerID": 412,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 412
+ }
+}
+```
+
+#### Example 3
+
+Expanding all the properties of `relatedEntities`:
+
+```qs
+employeeObject = employeeSelected.toObject("directReports.*")
+```
+
+```json
+{
+ "directReports": [
+ {
+ "ID": 418,
+ "firstName": "Lorena",
+ "lastName": "Boothe",
+ "salary": 44800,
+ "birthDate": "1970-10-02T00:00:00.000Z",
+ "woman": true,
+ "managerID": 413,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 413
+ }
+ },
+ {
+ "ID": 419,
+ "firstName": "Drew",
+ "lastName": "Caudill",
+ "salary": 41000,
+ "birthDate": "2030-01-12T00:00:00.000Z",
+ "woman": false,
+ "managerID": 413,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 413
+ }
+ },
+ {
+ "ID": 420,
+ "firstName": "Nathan",
+ "lastName": "Gomes",
+ "salary": 46300,
+ "birthDate": "2010-05-29T00:00:00.000Z",
+ "woman": false,
+ "managerID": 413,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 413
+ }
+ }
+ ]
+}
+```
+
+#### Example 4
+
+Extracting some properties of `relatedEntities`:
+
+```qs
+ employeeObject = employeeSelected.toObject("firstName, directReports.lastName")
+```
+
+Returns:
+
+```json
+{
+ "firstName": "Greg",
+ "directReports": [
+ {
+ "lastName": "Boothe"
+ },
+ {
+ "lastName": "Caudill"
+ },
+ {
+ "lastName": "Gomes"
+ }
+ ]
+}
+```
+
+#### Example 5
+
+Extracting a `relatedEntity` with simple form:
+
+```qs
+ coll = newCollection("firstName","employer")
+ employeeObject = employeeSelected.toObject(coll)
+```
+
+Returns:
+
+```json
+{
+ "firstName": "Greg",
+ "employer": {
+ "__KEY": 20
+ }
+}
+```
+
+#### Example 6
+
+Extracting all the properties of a `relatedEntity`:
+
+```qs
+ employeeObject = employeeSelected.toObject("employer.*")
+```
+
+Returns:
+
+```json
+{
+ "employer": {
+ "ID": 20,
+ "name": "India Astral Secretary",
+ "creationDate": "1984-08-25T00:00:00.000Z",
+ "revenues": 12000000,
+ "extra": null
+ }
+}
+```
+
+#### Example 7
+
+Extracting some properties of a `relatedEntity`:
+
+```qs
+ col = newCollection
+ col.push("employer.name")
+ col.push("employer.revenues")
+ employeeObject = employeeSelected.toObject(col)
+```
+
+Returns:
+
+```json
+{
+ "employer": {
+ "name": "India Astral Secretary",
+ "revenues": 12000000
+ }
+}
+```
+
+
+
+
+
+
+## .touched()
+
+
+**.touched()** : boolean
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|boolean|←|True if at least one entity attribute has been modified and not yet saved, else false|
+
+
+#### Description
+
+The `.touched()` function tests whether or not an entity attribute has been modified since the entity was loaded into memory or saved.
+
+If an attribute has been modified or calculated, the function returns true, else it returns false. You can use this function to determine if you need to save the entity.
+
+This function returns false for a new entity that has just been created (with [`.new( )`](DataClassClass.md#new)). Note however that if you use a function which calculates an attribute of the entity, the `.touched()` function will then return true. For example, if you call [`.getKey()`](#getkey) to calculate the primary key, `.touched()` returns true.
+
+#### Example
+
+In this example, we check to see if it is necessary to save the entity:
+
+```qs
+ var emp : cs.EmployeeEntity
+ emp = ds.Employee.get(672)
+ emp.firstName = emp.firstName //Even if updated with the same value, the attribute is marked as touched
+
+ if(emp.touched()) //if at least one of the attributes has been changed
+ emp.save()
+ end // otherwise, no need to save the entity
+```
+
+
+
+
+
+## .touchedAttributes( )
+
+
+**.touchedAttributes**() : collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|collection|←|Names of touched attributes, or empty collection|
+
+
+#### Description
+
+The `.touchedAttributes()` function returns the names of the attributes that have been modified since the entity was loaded into memory.
+
+This applies for attributes of the [kind](DataClassClass.md#attributename) `storage` or `relatedEntity`.
+
+In the case of a related entity having been touched (i.e., the foreign key), the name of the related entity and its primary key's name are returned.
+
+If no entity attribute has been touched, the method returns an empty collection.
+
+#### Example 1
+
+
+```qs
+ var touchedAttributes : collection
+ var emp : cs.EmployeeEntity
+
+ touchedAttributes = newCollection()
+ emp = ds.Employee.get(725)
+ emp.firstName = emp.firstName //Even if updated with the same value, the attribute is marked as touched
+ emp.lastName = "Martin"
+ touchedAttributes = emp.touchedAttributes()
+ //touchedAttributes: ["firstName","lastName"]
+```
+
+
+#### Example 2
+
+
+```qs
+ var touchedAttributes : collection
+ var emp : cs.EmployeeEntity
+ var company : cs.CompanyEntity
+
+ touchedAttributes = newCollection()
+
+ emp = ds.Employee.get(672)
+ emp.firstName = emp.firstName
+ emp.lastName = "Martin"
+
+ company = ds.Company.get(121)
+ emp.employer = company
+
+ touchedAttributes = emp.touchedAttributes()
+
+ //touchedAttributes: ["firstName","lastName","employer","employerID"]
+```
+
+In this case:
+
+* firstName and lastName have a `storage` kind
+* employer has a `relatedEntity` kind
+* employerID is the foreign key of the employer related entity
+
+
+
+
+
+## .unlock()
+
+
+**.unlock**() : object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|object|←|Status object|
+
+
+#### Description
+
+The `.unlock()` function removes the pessimistic lock on the record matching the entity in the datastore and table related to its dataclass.
+
+> For more information, please refer to [Entity locking](../qodlyScript/guides/data.md#entity-locking) section.
+
+A record is automatically unlocked when it is no longer referenced by any entities in the locking process (for example: if the lock is put only on one local reference of an entity, the entity and thus the record is unlocked when the process ends).
+
+>When a record is locked, it must be unlocked from the locking process and on the entity reference which put the lock. For example:
+
+```qs
+ e1 = ds.Emp.all()[0]
+ e2 = ds.Emp.all()[0]
+ res = e1.lock() //res.success = true
+ res = e2.unlock() //res.success = false
+ res = e1.unlock() //res.success = true
+```
+
+**Result**
+
+The object returned by `.unlock()` contains the following property:
+
+|Property| Type |Description|
+|---|---|---|
+|success| boolean| True if the unlock action is successful, false otherwise. If the unlock is done on a dropped entity, on a non locked record, or on a record locked by another process or entity, success is false.|
+
+#### Example
+
+```qs
+ var employee : cs.EmployeeEntity
+ var status : object
+ var info : string
+
+ employee = ds.Employee.get(725)
+ status = employee.lock()
+ ... //processing
+ status = employee.unlock()
+ if(status.success)
+ info = "The entity is now unlocked"
+ end
+```
+
+
+
+
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/EntitySelectionClass.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/EntitySelectionClass.md
new file mode 100644
index 000000000..dc3688695
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/EntitySelectionClass.md
@@ -0,0 +1,2254 @@
+---
+id: EntitySelectionClass
+title: EntitySelection
+---
+
+
+An entity selection is an object containing one or more reference(s) to [entities](../qodlyScript/guides/data-model.md#entity) belonging to the same [Dataclass](../qodlyScript/guides/data-model.md#dataclass). An entity selection can contain 0, 1 or X entities from the dataclass -- where X can represent the total number of entities contained in the dataclass.
+
+Entity selections can be created from existing selections using various functions of the [`DataClass` class](DataClassClass.md) such as [`.all()`](DataClassClass.md#all) or [`.query()`](DataClassClass.md#query), or functions of the `EntityClass` class itself, such as [`.and()`](#and) or [`orderBy()`](#orderby). You can also create blank entity selections using the [`dataClass.newSelection()`](DataClassClass.md#newselection) function.
+
+### Functions and properties
+
+||
+|---|
+|[](#index) |
+|[](#attributename) |
+|[](#add) |
+|[](#and) |
+|[](#attributename) |
+|[](#average) |
+|[](#clean) |
+|[](#contains) |
+|[](#count) |
+|[](#distinct) |
+|[](#distinctpaths) |
+|[](#drop) |
+|[](#extract) |
+|[](#first) |
+|[](#getdataclass) |
+|[](#isalterable) |
+|[](#isordered) |
+|[](#last) |
+|[](#length) |
+|[](#max) |
+|[](#min) |
+|[](#minus) |
+|[](#or) |
+|[](#orderby) |
+|[](#orderbyformula-) |
+|[](#query) |
+|[](#querypath) |
+|[](#queryplan) |
+|[](#selected) |
+|[](#slice) |
+|[](#sum) |
+|[](#tocollection-) |
+
+
+
+
+## [*index*]
+
+
+
+***[index]*** : 4D.Entity
+
+
+#### Description
+
+The `EntitySelection[index]` notation allows you to access entities within the entity selection using the standard collection syntax: pass the position of the entity you want to get in the *index* parameter.
+
+Note that the corresponding entity is reloaded from the datastore.
+
+*index* can be any number between 0 and [`.length`](#length)-1.
+
+* If *index* is out of range, an error is returned.
+* If *index* corresponds to a dropped entity, a Null value is returned.
+
+:::caution
+
+`EntitySelection[index]` is a [non assignable expression](basics/lang-expressions.md#assignable-vs-non-assignable-expressions), which means that it cannot be used as en editable entity reference with functions like [`.lock()`](EntityClass.md#lock) or [`.save()`](EntityClass.md#save). To work with the corresponding entity, you need to assign the returned expression to an assignable expression, such as a variable. Examples:
+
+```qs
+ sel = ds.Employee.all() //create the entity selection
+ //invalid statements:
+ result = sel[0].lock() //will NOT work
+ sel[0].lastName = "Smith" //will NOT work
+ result = sel[0].save() //will NOT work
+ //valid code:
+ entity = sel[0] //OK
+ entity.lastName = "Smith" //OK
+ entity.save() //OK
+```
+
+:::
+
+#### Example
+
+
+```qs
+ var employees : cs.EmployeeSelection
+ var employee : cs.EmployeeEntity
+ employees = ds.Employee.query("lastName = :1","H@")
+ employee = employees[2] // The 3rd entity of the employees entity selection is reloaded from the datastore
+```
+
+
+
+
+
+## .at()
+
+**.at**( *index* : integer ) : 4D.Entity
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|index|integer|→|Index of entity to return|
+|Result|4D.Entity |←|The entity at that index|
+
+
+#### Description
+
+The `.at()` function returns the entity at position *index*, allowing for positive and negative integer.
+
+If *index* is negative (from -1 to -n with n : length of the entity selection), the returned entity will be based on the reverse order of the entity selection.
+
+The function returns null if *index* is beyond entity selection limits.
+
+#### Example
+
+```qs
+var employees : cs.EmployeeSelection
+var emp1, emp2 : cs.EmployeeEntity
+employees = ds.Employee.query("lastName = :1","H@")
+emp1 = employees.at(2) //3rd entity of the employees entity selection
+emp2 = employees.at(-3) //starting from the end, 3rd entity
+ //of the employees entity selection
+```
+
+
+
+
+
+## .*attributeName*
+
+
+
+***.attributeName*** : collection ***.attributeName*** : 4D.EntitySelection
+
+
+#### Description
+
+Any dataclass attribute can be used as a property of an entity selection to return a "projection" of values for the attribute in the entity selection. Projected values can be a collection or a new entity selection, depending on the [kind](DataClassClass.md#attributename) (storage or relation) of the attribute.
+
+* If *attributeName* kind is `storage`:
+`.attributeName` returns a collection of values of the same type as *attributeName*.
+* If *attributeName* kind is `relatedEntity`:
+`.attributeName` returns a new entity selection of related values of the same type as *attributeName*. Duplications are removed (an unordered entity selection is returned).
+* If *attributeName* kind is `relatedEntities`:
+`.attributeName` returns a new entity selection of related values of the same type as *attributeName*. Duplications are removed (an unordered entity selection is returned).
+
+
+When a relation attribute is used as a property of an entity selection, the result is always another entity selection, even if only one entity is returned. In this case, if no entities are returned, the result is an empty entity selection.
+
+If the attribute does not exist in the entity selection, an error is returned.
+
+
+
+
+
+
+#### Example 1
+
+Projection of storage values:
+
+
+
+
+```qs
+ var firstNames : collection
+ entitySelection = ds.Employee.all()
+ firstNames = entitySelection.firstName // firstName type is string
+```
+
+The resulting collection is a collection of strings, for example:
+
+```json
+[
+ "Joanna",
+ "Alexandra",
+ "Rick"
+]
+```
+
+#### Example 2
+
+Projection of related entity:
+
+```qs
+ var es, entitySelection : cs.EmployeeSelection
+ entitySelection = ds.Employee.all()
+ es = entitySelection.employer // employer is related to a Company dataClass
+```
+
+The resulting object is an entity selection of Company with duplications removed (if any).
+
+#### Example 3
+
+Projection of related entities:
+
+```qs
+ var es : cs.EmployeeSelection
+ es = ds.Employee.all().directReports // directReports is related to Employee dataclass
+```
+
+The resulting object is an entity selection of Employee with duplications removed (if any).
+
+
+
+
+
+
+
+## .add()
+
+
+
+**.add**( *entity* : 4D.Entity ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|entity|4D.Entity|→|Entity to be added to the entity selection|
+|Result|4D.EntitySelection|→|Entity selection including the added *entity*|
+
+
+
+#### Description
+
+The `.add()` function adds the specified *entity* to the entity selection and returns the modified entity selection.
+
+>This function modifies the original entity selection.
+
+:::caution
+
+The entity selection must be *alterable*, i.e. it has been created for example by [`.newSelection()`](DataClassClass.md#newselection), otherwise `.add()` will return an error. Shareable entity selections do not accept the addition of entities. For more information, please refer to the [Shareable or alterable entity selections](../qodlyScript/guides/data.md#shareable-or-alterable-entity-selections) section.
+
+:::
+
+* If the entity selection is [ordered](../qodlyScript/guides/data-model.md#ordered-or-unordered-entity-selection), *entity* is added at the end of the selection. If a reference to the same entity already belongs to the entity selection, it is duplicated and a new reference is added.
+* If the entity selection is [unordered](../qodlyScript/guides/data-model.md#ordered-or-unordered-entity-selection), *entity* is added anywhere in the selection, with no specific order.
+
+The modified entity selection is returned by the function, so that function calls can be chained.
+
+An error occurs if *entity* and the entity selection are not related to the same Dataclass. If *entity* is Null, no error is raised.
+
+#### Example 1
+
+```qs
+ var employees : cs.EmployeeSelection
+ var employee : cs.EmployeeEntity
+ employees = ds.Employee.newSelection()
+ employee = ds.Employee.new()
+ employee.lastName = "Smith"
+ employee.save()
+ employees.add(employee) //The employee entity is added to the employees entity selection
+```
+
+#### Example 2
+
+Calls to the function can be chained:
+
+```qs
+ var sel : cs.ProductSelection
+ var p1,p2,p3 : cs.ProductEntity
+
+ p1 = ds.Product.get(10)
+ p2 = ds.Product.get(11)
+ p3 = ds.Product.get(12)
+ sel = ds.Product.newSelection()
+ sel = sel.add(p1).add(p2).add(p3)
+```
+
+
+
+
+
+
+## .and()
+
+
+**.and**( *entity* : 4D.Entity ) : 4D.EntitySelection **.and**( *entitySelection* : 4D.EntitySelection ) : 4D.EntitySelection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|entity |4D.Entity|→|Entity to intersect with|
+|entitySelection |4D.EntitySelection|→|Entity selection to intersect with|
+|Result|4D.EntitySelection|←|New entity selection with the result of intersection with logical AND operator|
+
+
+
+#### Description
+
+The `.and()` function combines the entity selection with an *entity* or *entitySelection* parameter using the logical AND operator; it returns a new, unordered entity selection that contains only the entities that are referenced in both the entity selection and the parameter.
+
+* If you pass *entity* as parameter, you combine this entity with the entity selection. If the entity belongs to the entity selection, a new entity selection containing only the entity is returned. Otherwise, an empty entity selection is returned.
+* If you pass *entitySelection* as parameter, you combine both entity selections. A new entity selection that contains only the entities that are referenced in both selections is returned. If there is no intersecting entity, an empty entity selection is returned.
+
+:::note
+
+You can compare [ordered and/or unordered entity selections](../qodlyScript/guides/data-model.md#ordered-or-unordered-entity-selection). The resulting selection is always unordered.
+
+:::
+
+If the original entity selection or the *entitySelection* parameter is empty, or if the *entity* is Null, an empty entity selection is returned.
+
+If the original entity selection and the parameter are not related to the same dataclass, an error is raised.
+
+
+#### Example 1
+
+
+```qs
+ var employees, result : cs.EmployeeSelection
+ var employee : cs.EmployeeEntity
+ employees = ds.Employee.query("lastName = :1","H@")
+ //The employees entity selection contains the entity
+ //with primary key 710 and other entities
+ //for ex. "Colin Hetrick" / "Grady Harness" / "Sherlock Holmes" (primary key 710)
+ employee = ds.Employee.get(710) // Returns "Sherlock Holmes"
+
+ result = employees.and(employee) //result is an entity selection containing
+ //only the entity with primary key 710 ("Sherlock Holmes")
+```
+
+
+#### Example 2
+
+We want to have a selection of employees named "Jones" who live in New York:
+
+```qs
+ var sel1, sel2, sel3 : cs.EmployeeSelection
+ sel1 = ds.Employee.query("name = :1","Jones")
+ sel2 = ds.Employee.query("city = :1","New York")
+ sel3 = sel1.and(sel2)
+```
+
+
+
+
+
+## .average()
+
+
+
+**.average**( *attributePath* : string ) : number
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attributePath |string|→|Attribute path to be used for calculation|
+|Result|number|←|Arithmetic mean (average) of entity attribute values (Undefined if empty entity selection)|
+
+
+#### Description
+
+The `.average()` function returns the arithmetic mean (average) of all the non-null values of *attributePath* in the entity selection.
+
+Pass in the *attributePath* parameter the attribute path to evaluate.
+
+Only numerical values are taken into account for the calculation. Note however that, if the *attributePath* of the entity selection contains mixed value types, `.average()` takes all scalar elements into account to calculate the average value.
+
+:::note
+
+Date values are converted to numerical values (seconds) and used to calculate the average.
+
+:::
+
+`.average()` returns **undefined** if the entity selection is empty or *attributePath* does not contain numerical values.
+
+An error is returned if:
+
+* *attributePath* is a related attribute,
+* *attributePath* designates an attribute that does not exist in the entity selection dataclass.
+
+
+#### Example
+
+We want to obtain a list of employees whose salary is higher than the average salary:
+
+```qs
+ var averageSalary : number
+ var moreThanAv : cs.EmployeeSelection
+ averageSalary = ds.Employee.all().average("salary")
+ moreThanAv = ds.Employee.query("salary > :1",averageSalary)
+```
+
+
+
+
+## .clean()
+
+
+**.clean**() : 4D.EntitySelection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.EntitySelection|←|New entity selection without deleted entities|
+
+
+#### Description
+
+The `.clean()` function returns a new entity selection based upon the original entity selection but without deleted entities, if any.
+
+By default, when an entity is [dropped](EntitySelectionClass.md#drop), its reference(s) in existing entity selection(s) become *undefined* but are not removed from the entity selection object(s). Deleted entities are still included in the [`.length`](#length) property and are displayed as blank lines if the entity selection is bound to an interface object such as a list. In this case, calling the `.clean()` function on the entity selection allows you to get a new, up-to-date entity selection, not containing *undefined* entity references.
+
+The resulting entity selection keeps the same [order criteria](../qodlyScript/guides/data-model.md#ordered-or-unordered-entity-selection) and the same [alterable/shareable](../qodlyScript/guides/data.md#shareable-or-alterable-entity-selections) property as the original entity selection.
+
+
+#### Example
+
+```qs
+var sel , sel2 : cs.SpecialitySelection
+var status : object
+
+sel = ds.Speciality.query("ID <= 4")
+status = ds.Speciality.get(2).drop() //delete the entity from the dataclass
+ //sel.length == 4
+
+sel2 = sel.clean()
+ //sel2.length == 3
+```
+
+
+
+
+
+
+
+## .contains()
+
+
+**.contains**( *entity* : 4D.Entity ) : boolean
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|entity|4D.Entity|→|Entity to evaluate|
+|Result|boolean|←|True if the entity belongs to the entity selection, else false|
+
+
+#### Description
+
+The `.contains()` function returns true if entity reference belongs to the entity selection, and false otherwise.
+
+In *entity*, specify the entity to search for in the entity selection. If entity is null, the function will return false.
+
+If *entity* and the entity selection do not belong to the same dataclass, an error is raised.
+
+#### Example
+
+```qs
+ var employees : cs.EmployeeSelection
+ var employee : cs.EmployeeEntity
+ var info : string
+
+ employees = ds.Employee.query("lastName = :1","H@")
+ employee = ds.Employee.get(610)
+
+ if(employees.contains(employee))
+ info = "The entity with primary key 610 has a last name beginning with H"
+ else
+ info = "The entity with primary key 610 does not have a last name beginning with H"
+ end
+```
+
+
+
+
+
+
+## .count()
+
+
+
+**.count**( *attributePath* : string ) : number
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attributePath |string|→|Path of the attribute to be used for calculation|
+|Result|number|←|Number of non null *attributePath* values in the entity selection|
+
+
+#### Description
+
+The `.count()` function returns the number of entities in the entity selection with a non-null value in *attributePath*.
+
+>Only scalar values are taken into account. object or collection type values are considered as null values.
+
+An error is returned if:
+
+* *attributePath* is a related attribute,
+* *attributePath* is not found in the entity selection dataclass.
+
+#### Example
+
+We want to find out the total number of employees for a company without counting any whose job title has not been specified:
+
+```qs
+ var sel : cs.EmployeeSelection
+ var count : number
+
+ sel = ds.Employee.query("employer = :1","Acme, Inc")
+ count = sel.count("jobtitle")
+```
+
+
+
+
+
+## .copy()
+
+
+
+**.copy**( \{ *option* : integer \} ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|option |integer|→|`kShared`: return a shareable entity selection|
+|Result|4D.EntitySelection|←|Copy of the entity selection|
+
+
+#### Description
+
+The `.copy()` function returns a copy of the original entity selection.
+
+> This function does not modify the original entity selection.
+
+By default, if the *option* parameter is omitted, the function returns a new, [alterable](../qodlyScript/guides/data.md#shareable-or-alterable-entity-selections) entity selection (even if the function is applied to a [shareable](../qodlyScript/guides/data.md#shareable-or-alterable-entity-selections) entity selection). Pass the `kShared` constant in the *option* parameter if you want to create a shareable entity selection.
+
+
+#### Example
+
+You want to copy a regular entity selection to a shared entity selection in the `storage`:
+
+```qs
+localSel : cs.ProductSelection
+localSel = ds.Products.newSelection() //create an empty selection
+...
+ // The localSel entity selection is updated with another one
+localSel.add(selectedProducts)
+
+use(storage)
+ if(storage.products == null)
+ storage.products = newSharedObject()
+ end
+
+ use(storage.products)
+ storage.products = localSel.copy(kShared)
+ end
+end
+```
+
+
+
+
+
+## .distinct()
+
+
+
+**.distinct**( *attributePath* : string \{ , *option* : integer \} ) : collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attributePath|string|→|Path of attribute whose distinct values you want to get|
+|option|integer|→|`kDiacritical`, `kCountValues`|
+|Result|collection|←|collection with only distinct values|
+
+
+#### Description
+
+The `.distinct()` function returns a collection containing only distinct (different) values from the *attributePath* in the entity selection.
+
+The returned collection is automatically sorted. **Null** values are not returned.
+
+In the *attributePath* parameter, pass the entity attribute whose distinct values you want to get. Only scalar values (text, number, boolean, or date) can be handled. If the *attributePath* leads to an object property that contains values of different types, they are first grouped by type and sorted afterwards. Types are returned in the following order:
+
+1. booleans
+2. strings
+3. numbers
+4. dates
+
+You can use the `[]` notation to designate a collection when *attributePath* is a path within an object (see examples).
+
+In the *options* parameter, you can pass one or a combination of the following constants:
+
+|Constant|Value|Comment|
+|---|---|---|
+|`kDiacritical`|8|Evaluation is case sensitive and differentiates accented characters. By default if omitted, a non-diacritical evaluation is performed|
+|`kCountValues`|32|Return the count of entities for every distinct value. When this option is passed, `.distinct()` returns a collection of objects containing a pair of `{"value":*value*; "count":*count*}` properties.|
+
+:::note
+
+The `kCountValues` option is only available with storage attributes of type boolean, string, number, and date.
+
+:::
+
+By default, a non-diacritical evaluation is performed. If you want the evaluation to be case sensitive or to differentiate accented characters, pass the `kDiacritical` constant in the *option* parameter.
+
+An error is returned if:
+
+* *attributePath* is a related attribute,
+* *attributePath* is not found in the entity selection dataclass.
+
+#### Examples
+
+You want to get a collection containing a single element per country name:
+
+```qs
+ var countries : collection
+ countries = ds.Employee.all().distinct("address.country")
+```
+
+`nicknames` is a collection and `extra` is an object attribute:
+
+```qs
+values = ds.Employee.all().distinct("extra.nicknames[].first")
+```
+
+You want to get the number of different job names in the company:
+
+```qs
+var jobs : collection
+jobs = ds.Employee.all().distinct("jobName",kCountValues)
+//jobs[0] = {"value":"Developer","count":17}
+//jobs[1] = {"value":"Office manager","count":5}
+//jobs[2] = {"value":"Accountant","count":2}
+//...
+```
+
+
+
+
+
+## .distinctPaths()
+
+**.distinctPaths**( *attribute* : string ) : collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attribute|string|→|object attribute name whose paths you want to get|
+|Result|collection|←|newCollection with distinct paths|
+
+
+#### Description
+
+The `.distinctPaths()` function returns a collection of distinct paths found in the indexed object *attribute* for the entity selection.
+
+If *attribute* is not an indexed object attribute, an error is generated.
+
+After the call, the size of the returned collection is equal to the number of distinct paths found in *attribute* for the entity selection. Paths are returned as strings including nested attributes and collections, for example "info.address.number" or "children[].birthdate". Entities with a null value in the *attribute* are not taken into account.
+
+#### Example
+
+You want to get all paths stored in a *fullData* object attribute:
+
+```qs
+var paths : collection
+paths = ds.Employee.all().distinctPaths("fullData")
+//paths[0] = "age"
+//paths[1] = "Children"
+//paths[2] = "Children[].age"
+//paths[3] = "Children[].name"
+//paths[4] = "Children.length"
+///...
+```
+
+:::note
+
+*length* is automatically added as path for nested collection properties.
+
+:::
+
+
+
+
+
+## .drop()
+
+
+
+**.drop**( \{ *mode* : integer \} ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|mode|integer|→|`kStopDroppingOnFirstError`: stops method execution on first non-droppable entity|
+|Result|4D.EntitySelection|←|Empty entity selection if successful, else entity selection containing non-droppable entity(ies)|
+
+
+#### Description
+
+The `.drop()` function removes the entities belonging to the entity selection from the table related to its dataclass within the datastore. The entity selection remains in memory.
+
+:::caution
+
+Removing entities is permanent and cannot be undone. It is recommended to call this action in a transaction in order to have a rollback option.
+
+:::
+
+If a locked entity is encountered during the execution of `.drop()`, it is not removed. By default, the function processes all entities of the entity selection and returns non-droppable entities in the entity selection. If you want the method to stop execution at the first encountered non-droppable entity, pass the `kStopDroppingOnFirstError` constant in the *mode* parameter.
+
+#### Example
+
+Example without the `kStopDroppingOnFirstError` option:
+
+```qs
+ var employees, notDropped : cs.EmployeeSelection
+ var info : string
+ employees = ds.Employee.query("firstName = :1","S@")
+ notDropped = employees.drop() // notDropped is an entity selection containing all the not dropped entities
+ if(notDropped.length == 0) //The delete action is successful, all the entities have been deleted
+ info = "You have dropped "+string(employees.length)+" employees" //The dropped entity selection remains in memory
+ else
+ info = "Problem during drop, try later"
+ end
+```
+
+Example with the `kStopDroppingOnFirstError` option:
+
+```qs
+ var employees, notDropped : cs.EmployeeSelection
+ var info : string
+ employees = ds.Employee.query("firstName = :1","S@")
+ notDropped = employees.drop(kStopDroppingOnFirstError) //notDropped is an entity selection containing the first not dropped entity
+ if(notDropped.length == 0) //The delete action is successful, all the entities have been deleted
+ info = "You have dropped "+string(employees.length)+" employees") //The dropped entity selection remains in memory
+ else
+ info = "Problem during drop, try later"
+ end
+```
+
+
+
+
+
+
+
+## .extract()
+
+
+**.extract**( *attributePath* : string \{ , *option* : integer \} ) : collection **.extract**( *attributePath* \{ , *targetPath* } \{ , *...attributePathN* : string , *targetPathN* : string \} ) : collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attributePath |string|→|Attribute path whose values must be extracted to the new collection |
+|targetPath|string|→|Target attribute path or attribute name|
+|option|integer|→|`kKeepNull`: include null attributes in the returned collection (ignored by default)|
+|Result|collection|←|collection containing extracted values|
+
+
+#### Description
+
+The `.extract()` function returns a collection containing *attributePath* values extracted from the entity selection.
+
+*attributePath* can refer to:
+
+* a scalar dataclass attribute,
+* related entity,
+* related entities.
+
+If *attributePath* is invalid, an empty collection is returned.
+
+This function accepts two syntaxes.
+
+**.extract( attributePath : string \{ , option : integer } ) : collection**
+
+With this syntax, `.extract()` populates the returned collection with the *attributePath* values of the entity selection.
+
+By default, entities for which *attributePath* is *null* or undefined are ignored in the resulting collection. You can pass the `kKeepNull` constant in the *option* parameter to include these values as **null** elements in the returned collection.
+
+* Dataclass attributes with [.kind](DataClassClass.md#attributename) = "relatedEntity" are extracted as a collection of entities (duplications are kept).
+* Dataclass attributes with [.kind](DataClassClass.md#attributename) = "relatedEntities" are extracted as a collection of entity selections.
+
+
+**.extract ( attributePath , targetPath \{ , ...attributePathN , ... targetPathN\}) : collection**
+
+With this syntax, `.extract()` populates the returned collection with the *attributePath* properties. Each element of the returned collection is an object with *targetPath* properties filled with the corresponding *attributePath* properties. Null values are kept (*option* parameter is ignored with this syntax).
+
+If several *attributePath* are given, a *targetPath* must be given for each. Only valid pairs [*attributePath*, *targetPath*] are extracted.
+
+* Dataclass attributes with [.kind](DataClassClass.md#attributename) = "relatedEntity" are extracted as an entity.
+* Dataclass attributes with [.kind](DataClassClass.md#attributename) = "relatedEntities" are extracted as an entity selection.
+
+:::note
+
+Entities of a collection of entities accessed by [] are not reloaded from the datastore.
+
+:::
+
+#### Example
+
+Given the following table and relation:
+
+
+
+```qs
+ var firstnames, addresses, mailing, teachers : collection
+ //
+ //
+ //firstnames is a collection of Strings
+
+
+ firstnames = ds.Teachers.all().extract("firstname")
+ //
+ //addresses is a collection of entities related to dataclass Address
+ //Null values for address are extracted
+ addresses = ds.Teachers.all().extract("address",kKeepNull)
+ //
+ //
+ //mailing is a collection of objects with properties "who" and "to"
+ //"who" property content is string type
+ //"to" property content is entity type (Address dataclass)
+ mailing = ds.Teachers.all().extract("lastname","who","address","to")
+ //
+ //
+ //mailing is a collection of objects with properties "who" and "city"
+ //"who" property content is string type
+ //"city" property content is string type
+ mailing = ds.Teachers.all().extract("lastname","who","address.city","city")
+ //
+ //teachers is a collection of objects with properties "where" and "who"
+ //"where" property content is string
+ //"who" property content is an entity selection (Teachers dataclass)
+ teachers = ds.Address.all().extract("city","where","teachers","who")
+ //
+ //teachers is a collection of entity selections
+ teachers = ds.Address.all().extract("teachers")
+```
+
+
+
+
+
+
+## .first()
+
+
+
+**.first()** : 4D.Entity
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.Entity|←|Reference to the first entity of the entity selection (Null if selection is empty)|
+
+
+#### Description
+
+The `.first()` function returns a reference to the entity in the first position of the entity selection.
+
+The result of this function is similar to:
+
+```qs
+ entity = entitySel[0]
+```
+
+There is, however, a difference between both statements when the selection is empty:
+
+
+```qs
+ var entitySel : cs.EmpSelection
+ var entity : cs.EmpEntity
+ entitySel = ds.Emp.query("lastName = :1","Nonexistentname") //no matching entity
+ //entity selection is then empty
+ entity = entitySel.first() //returns Null
+ entity = entitySel[0] //generates an error
+```
+
+#### Example
+
+
+```qs
+ var entitySelection : cs.EmpSelection
+ var entity : cs.EmpEntity
+ entitySelection = ds.Emp.query("salary > :1",100000)
+ if(entitySelection.length != 0)
+ entity = entitySelection.first()
+ end
+```
+
+
+
+
+
+
+## .getDataClass()
+
+
+
+
+**.getDataClass()** : 4D.DataClass
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.DataClass|←|Dataclass object to which the entity selection belongs|
+
+
+#### Description
+
+The `.getDataClass()` function returns the dataclass of the entity selection.
+
+This function is mainly useful in the context of generic code.
+
+#### Example
+
+The following generic code duplicates all entities of the entity selection:
+
+```qs
+ //duplicate_entities method
+ //duplicate_entities(entity_selection)
+
+ declare ( entitySelection : 4D.EntitySelection )
+ var dataClass : 4D.DataClass
+ var entity, duplicate : 4D.Entity
+ var status : object
+ dataClass = entitySelection.getDataClass()
+ forEach(entity,entitySelection)
+ duplicate = dataClass.new()
+ duplicate.fromObject(entity.toObject())
+ duplicate[dataClass.getInfo().primaryKey] = null //reset the primary key
+ status = duplicate.save()
+ end
+```
+
+
+
+
+
+## .isAlterable()
+
+
+
+**.isAlterable**() : boolean
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|boolean|←|True if the entity selection is alterable, False otherwise|
+
+
+#### Description
+
+The `.isAlterable()` function returns true if the entity selection is alterable, and false if the entity selection is not alterable.
+
+For more information, please refer to the [Shareable or alterable entity selections](../qodlyScript/guides/data.md#shareable-or-alterable-entity-selections) section.
+
+
+
+
+
+
+## .isOrdered()
+
+
+
+**.isOrdered**() : boolean
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|boolean|←|true if the entity selection is ordered, false otherwise|
+
+
+#### Description
+
+The `.isOrdered()` function returns true if the entity selection is ordered, and false if it is unordered.
+
+For more information, please refer to the [Ordered or unordered entity selection](../qodlyScript/guides/data-model.md#ordered-or-unordered-entity-selection) section.
+
+
+#### Example
+
+
+```qs
+ var employees : cs.EmployeeSelection
+ var employee : cs.EmployeeEntity
+ var isOrdered : boolean
+ var info : string
+ employees = ds.Employee.newSelection(kKeepOrdered)
+ employee = ds.Employee.get(714) // Gets the entity with primary key 714
+
+ //In an ordered entity selection, we can add the same entity several times (duplications are kept)
+ employees.add(employee)
+ employees.add(employee)
+ employees.add(employee)
+
+ isOrdered = employees.isOrdered()
+ if(isOrdered)
+ info = "The entity selection is ordered and contains "+string(employees.length)+" employees"
+ end
+```
+
+
+
+
+
+
+
+## .last()
+
+
+
+**.last**() : 4D.Entity
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|4D.Entity |←|Reference to the last entity of the entity selection (null if empty entity selection)|
+
+
+#### Description
+
+The `.last()` function returns a reference to the entity in last position of the entity selection.
+
+The result of this function is similar to:
+
+```qs
+ entity = entitySel[length-1]
+```
+
+If the entity selection is empty, the function returns null.
+
+
+#### Example
+
+
+```qs
+ var entitySelection : cs.EmpSelection
+ var entity : cs.EmpEntity
+ entitySelection = ds.Emp.query("salary < :1",50000)
+ if(entitySelection.length != 0)
+ entity = entitySelection.last()
+ end
+```
+
+
+
+
+
+
+## .length
+
+
+
+**.length** : integer
+
+
+#### Description
+
+The `.length` property returns the number of entities in the entity selection. If the entity selection is empty, it returns 0.
+
+Entity selections always have a `.length` property.
+
+
+#### Example
+
+```qs
+ var vSize : integer
+ var info : string
+ vSize = ds.Employee.query("gender = :1","male").length
+ info = string(vSize)+" male employees found."
+```
+
+
+
+
+
+
+## .max()
+
+
+
+**.max**( *attributePath* : string ) : any
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attributePath |string|→|Path of the attribute to be used for calculation|
+|Result|any|←|Highest value of attribute|
+
+
+#### Description
+
+The `.max()` function returns the highest (or maximum) value among all the values of *attributePath* in the entity selection. It actually returns the value of the last entity of the entity selection as it would be sorted in ascending order using the [`.orderBy()`](#orderby) function.
+
+If you pass in *attributePath* a path to an object property containing different types of values, the `.max()` function will return the maximum value of the first scalar type found in the default type list order:
+
+1. booleans
+2. strings
+3. numbers
+4. objects
+5. collections
+6. dates
+
+`.max()` returns **undefined** if the entity selection is empty or *attributePath* is not found in the object attribute.
+
+
+An error is returned if:
+
+* *attributePath* is a related attribute,
+* *attributePath* designates an attribute that does not exist in the entity selection dataclass.
+
+
+
+#### Example
+
+We want to find the highest salary among all the female employees:
+
+```qs
+ var sel : cs.EmpSelection
+ var maxSalary : number
+ sel = ds.Employee.query("gender = :1","female")
+ maxSalary = sel.max("salary")
+```
+
+
+
+
+
+## .min()
+
+
+
+**.min**( *attributePath* : string ) : any
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attributePath |string|→|Path of the attribute to be used for calculation|
+|Result|any|←|Lowest value of attribute|
+
+
+#### Description
+
+The `.min()` function returns the lowest (or minimum) value among all the values of *attributePath* in the entity selection. It actually returns the first entity of the entity selection as it would be sorted in ascending order using the [`.orderBy()`](#orderby) function (excluding **null** values).
+
+If you pass in *attributePath* a path to an object property containing different types of values, the `.min()` function will return the minimum value within the first scalar value type found in the default type list order (see [`.max()`](#max) description).
+
+`.min()` returns **undefined** if the entity selection is empty or *attributePath* is not found in the object attribute.
+
+An error is returned if:
+
+* *attributePath* is a related attribute,
+* *attributePath* designates an attribute that does not exist in the entity selection dataclass.
+
+
+#### Example
+
+In this example, we want to find the lowest salary among all the female employees:
+
+```qs
+ var sel : cs.EmpSelection
+ var minSalary : number
+ sel = ds.Employee.query("gender = :1","female")
+ minSalary: = sel.min("salary")
+```
+
+
+
+
+
+## .minus()
+
+
+
+**.minus**( *entity* : 4D.Entity ) : 4D.EntitySelection **.minus**( *entitySelection* : 4D.EntitySelection ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|entity |4D.Entity|→|Entity to substract|
+|entitySelection|4D.EntitySelection|→|Entity selection to substract|
+|Result|4D.EntitySelection|←|New entity selection or a new reference on the existing entity selection|
+
+
+#### Description
+
+The `.minus()` function excludes from the entity selection to which it is applied the *entity* or the entities of *entitySelection* and returns the resulting entity selection.
+
+* If you pass *entity* as parameter, the function creates a new entity selection without *entity* (if *entity* belongs to the entity selection). If *entity* was not included in the original entity selection, a new reference to the entity selection is returned.
+* If you pass *entitySelection* as parameter, the function returns an entity selection containing the entities belonging to the original entity selection without the entities belonging to *entitySelection*.
+
+>You can compare [ordered and/or unordered entity selections](../qodlyScript/guides/data-model.md#ordered-or-unordered-entity-selection). The resulting selection is always unordered.
+
+If the original entity selection or both the original entity selection and the *entitySelection* parameter are empty, an empty entity selection is returned.
+
+If *entitySelection* is empty or if *entity* is null, a new reference to the original entity selection is returned.
+
+If the original entity selection and the parameter are not related to the same dataclass, an error is raised.
+
+
+#### Example 1
+
+```qs
+ var employees, result : cs.EmployeeSelection
+ var employee : cs.EmployeeEntity
+
+ employees = ds.Employee.query("lastName = :1","H@")
+ // The employees entity selection contains the entity with primary key 710 and other entities
+ // for ex. "Colin Hetrick", "Grady Harness", "Sherlock Holmes" (primary key 710)
+
+ employee = ds.Employee.get(710) // Returns "Sherlock Holmes"
+
+ result = employees.minus(employee) //result contains "Colin Hetrick", "Grady Harness"
+```
+
+
+
+#### Example 2
+
+We want to have a selection of female employees named "Jones" who live in New York :
+
+```qs
+ var sel1, sel2, sel3 : cs.EmployeeSelection
+ sel1 = ds.Employee.query("name = :1","Jones")
+ sel2 = ds.Employee.query("city = :1","New York")
+ sel3 = sel1.and(sel2).minus(ds.Employee.query("gender = 'male'"))
+```
+
+
+
+
+
+## .or()
+
+
+**.or**( *entity* : 4D.Entity ) : 4D.EntitySelection **.or**( *entitySelection* : 4D.EntitySelection ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|entity|4D.Entity|→|Entity to intersect with|
+|entitySelection|4D.EntitySelection|→|Entity selection to intersect with|
+|Result|4D.EntitySelection|←|New entity selection or new reference to the original entity selection|
+
+
+#### Description
+
+The `.or()` function combines the entity selection with the *entity* or *entitySelection* parameter using the logical (not exclusive) OR operator; it returns a new, unordered entity selection that contains all the entities from the entity selection and the parameter.
+
+* If you pass *entity* as parameter, you compare this entity with the entity selection. If the entity belongs to the entity selection, a new reference to the entity selection is returned. Otherwise, a new entity selection containing the original entity selection and the entity is returned.
+* If you pass *entitySelection* as parameter, you compare entity selections. A new entity selection containing the entities belonging to the original entity selection or *entitySelection* is returned (or is not exclusive, entities referenced in both selections are not duplicated in the resulting selection).
+
+>You can compare [ordered and/or unordered entity selections](../qodlyScript/guides/data-model.md#ordered-or-unordered-entity-selection). The resulting selection is always unordered.
+
+If the original entity selection and the *entitySelection* parameter are empty, an empty entity selection is returned. If the original entity selection is empty, a reference to *entitySelection* or an entity selection containing only *entity* is returned.
+
+If *entitySelection* is empty or if *entity* is Null, a new reference to the original entity selection is returned.
+
+If the original entity selection and the parameter are not related to the same dataclass, an error is raised.
+
+
+#### Example 1
+
+```qs
+ var employees1, employees2, result : cs.EmployeeSelection
+ employees1 = ds.Employee.query("lastName = :1","H@") //Returns "Colin Hetrick","Grady Harness"
+ employees2 = ds.Employee.query("firstName = :1","C@") //Returns "Colin Hetrick", "Cath Kidston"
+ result = employees1.or(employees2) //result contains "Colin Hetrick", "Grady Harness","Cath Kidston"
+```
+
+#### Example 2
+
+```qs
+ var employees, result : cs.EmployeeSelection
+ var employee : cs.EmployeeEntity
+ employees = ds.Employee.query("lastName = :1","H@") // Returns "Colin Hetrick","Grady Harness", "Sherlock Holmes"
+ employee = ds.Employee.get(686) //the entity with primary key 686 does not belong to the $employees entity selection
+ //It matches the employee "Mary Smith"
+
+ result = employees.or(employee) //result contains "Colin Hetrick", "Grady Harness", "Sherlock Holmes", "Mary Smith"
+```
+
+
+
+
+
+## .orderBy()
+
+
+**.orderBy**( *pathString* : string ) : 4D.EntitySelection **.orderBy**( *pathObjects* : collection ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|pathString |string |→|Attribute path(s) and sorting instruction(s) for the entity selection|
+|pathObjects |collection|→|collection of criteria objects|
+|Result|4D.EntitySelection|←|New entity selection in the specified order|
+
+
+#### Description
+
+The `.orderBy()` function returns a new [ordered](../qodlyScript/guides/data-model.md#ordered-or-unordered-entity-selection) entity selection containing all entities of the entity selection in the order specified by *pathString* or *pathObjects* criteria.
+
+> This function does not modify the original entity selection.
+
+You must use a criteria parameter to define how the entities must be sorted. Two different parameters are supported:
+
+* *pathString* (string): this parameter contains a formula made of 1 to X attribute paths and (optionally) sort orders, separated by commas. The syntax is:
+
+```qs
+"attributePath1 {desc or asc}, attributePath2 {desc or asc},..."
+```
+
+The order in which the attributes are passed determines the sorting priority of the entities. By default, attributes are sorted in ascending order. You can set the sort order of a property in the criteria string, separated from the property path by a single space: pass "asc" to sort in ascending order or "desc" in descending order.
+
+* *pathObjects* (collection): each element of the collection contains an object structured in the following way:
+
+```json
+{
+ "propertyPath": string,
+ "descending": boolean
+}
+```
+
+By default, attributes are sorted in ascending order ("descending" is false).
+
+You can add as many objects in the criteria collection as necessary.
+
+>Null values are evaluated as less than other values.
+
+#### Example
+
+
+```qs
+// order by formula
+ sortedEntitySelection = entitySelection.orderBy("firstName asc, salary desc")
+ sortedEntitySelection = entitySelection.orderBy("firstName")
+
+ // order by collection with or without sort orders
+ orderColl = newCollection
+ orderColl.push(newObject("propertyPath","firstName","descending",false))
+ orderColl.push(newObject("propertyPath","salary","descending",true))
+ sortedEntitySelection = entitySelection.orderBy(orderColl)
+
+ orderColl = newCollection
+ orderColl.push(newObject("propertyPath","manager.lastName"))
+ orderColl.push(newObject("propertyPath","salary"))
+ sortedEntitySelection = entitySelection.orderBy(orderColl)
+```
+
+
+
+
+
+
+## .orderByFormula( )
+
+
+
+**.orderByFormula**( *formulaString* : string \{ , *sortOrder* : integer \} \{ , *settings* : object\} ) : 4D.EntitySelection **.orderByFormula**( *formulaObj* : object \{ , *sortOrder* : integer \} \{ , *settings* : object} ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|formulaString|string|→|formula string|
+|formulaObj|4D.Formula|→|formula object|
+|sortOrder |integer|→|`kAscending` (default) or `kDescending`|
+|settings|object|→|Parameter(s) for the formula|
+|Result|4D.EntitySelection|←|New ordered entity selection|
+
+
+#### Description
+
+The `.orderByFormula()` function returns a new, ordered entity selection containing all entities of the entity selection in the order defined through the *formulaString* or *formulaObj* and, optionally, *sortOrder* and *settings* parameters.
+
+>This function does not modify the original entity selection.
+
+You can use either a *formulaString* or a *formulaObj* parameter:
+
+- *formulaString*: you pass a QodlyScript expression such as "Year of(this.birthDate)".
+- *formulaObj*: pass a valid formula object created using the `formula` or `formula from string` command.
+
+The *formulaString* or *formulaObj* is executed for each entity of the entity selection and its result is used to define the position of the entity in the returned entity selection. The result must be of a sortable type (boolean, date, number, text, time, null).
+
+>A null result is always the smallest value.
+
+By default if you omit the *sortOrder* parameter, the resulting entity selection is sorted in ascending order. Optionnally, you can pass one of the following values in the *sortOrder* parameter:
+
+|Constant| Value| Comment|
+|---|---|---|
+|kAscending| 0 |Ascending sort order (default)|
+|kDescending| 1 |Descending sort order|
+
+Within the *formulaString* or *formulaObj*, the processed entity and thus its attributes are available through the `This` command (for example, `this.lastName`).
+
+You can pass parameter(s) to the formula using the `args` property (object) of the `settings` parameter: the formula receives the `settings.args` object in a special variable named `$1`.
+
+#### Example 1
+
+Sorting students using a formula provided as text:
+
+```qs
+ var es1, es2 : cs.StudentsSelection
+ es1 = ds.Students.query("nationality = :1","French")
+ es2 = es1.orderByFormula("length(this.lastname)") //ascending by default
+ es2 = es1.orderByFormula("length(this.lastname)",kDescending)
+```
+
+Same sort order but using a formula object:
+
+```qs
+ var es1, es2 : cs.StudentsSelection
+ var vFormula : 4D.Function
+ es1 = ds.Students.query("nationality = :1","French")
+ vFormula = formula(length(this.lastname))
+ es2 = es1.orderByFormula(vFormula) // ascending by default
+ es2 = es1.orderByFormula(vFormula,kDescending)
+```
+
+
+#### Example 2
+
+A formula is given as a formula object with parameters; `settings.args` object is received in the `$1` variable in the ***computeAverage*** method.
+
+In this example, the "marks" object field in the **Students** dataClass contains students' grades for each subject. A single formula object is used to compute a student's average grade with different coefficients for schoolA and schoolB.
+
+```qs
+ var es1, es2 : cs.StudentsSelection
+ var vFormula, schoolA, schoolB : object
+ es1 = ds.Students.query("nationality = :1","French")
+ vFormula = formula(computeAverage($1))
+
+ schoolA = newObject() //settings object
+ schoolA.args = newObject("english",1,"math",1,"history",1) // Coefficients to compute an average
+
+ //Order students according to school A criteria
+ es2 = es1.entitySelection.orderByFormula(vFormula,schoolA)
+
+ schoolB = newObject() //settings object
+ schoolB.args = newObject("english",1,"math",2,"history",3) // Coefficients to compute an average
+
+ //Order students according to school B criteria
+ es2 = es1.entitySelection.orderByFormula(vFormula,kDescending,schoolB)
+```
+
+```qs
+ //
+ // computeAverage method
+ // -----------------------------
+ declare (coefList : object) -> result : integer
+ var subject : string
+ var vAverage, vSum : integer
+
+ vAverage = 0
+ vSum = 0
+
+ forEach(subject,coefList)
+ vSum = vSum+coefList[subject]
+ end
+
+ forEach(subject,this.marks)
+ vAverage = vAverage+(this.marks[subject]*coefList[subject])
+ end
+
+ result = vAverage/vSum
+```
+
+
+
+
+
+
+## .query()
+
+
+
+**.query**( *queryString* : string \{ , *...value* : any \} \{ , *querySettings* : object \} ) : 4D.EntitySelection **.query**( *formula* : object \{ , *querySettings* : object \} ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|queryString |string |→ |Search criteria as string|
+|formula |4D.Formula |→ |Search criteria as formula object|
+|value|any|→|Value(s) to use for indexed placeholder(s)|
+|querySettings|object|→|Query options: parameters, attributes, args, allowFormulas, context, queryPath, queryPlan|
+|Result|4D.EntitySelection|←|New entity selection made up of entities from entity selection meeting the search criteria specified in *queryString* or *formula*|
+
+#### Description
+
+The `.query()` function searches for entities that meet the search criteria specified in *queryString* or *formula* and (optionally) *value*(s) among all the entities in the entity selection, and returns a new object of type `EntitySelection` containing all the entities that are found. Lazy loading is applied.
+
+>This function does not modify the original entity selection.
+
+If no matching entities are found, an empty `EntitySelection` is returned.
+
+For detailed information on how to build a query using *queryString*, *value*, and *querySettings* parameters, please refer to the DataClass [`.query()`](DataClassClass.md#query) function description.
+
+>By default if you omit the **order by** statement in the *queryString*, the returned entity selection is [not ordered](../qodlyScript/guides/data-model.md#ordered-or-unordered-entity-selection).
+
+#### Example 1
+
+
+```qs
+ var entitySelectionTemp, myEntitySel : cs.EmployeeSelection
+ entitySelectionTemp = ds.Employee.query("lastName = :1","M@")
+ myEntitySel = entitySelectionTemp.query("manager.lastName = :1","S@")
+```
+
+
+#### Example 2
+
+More examples of queries can be found in the DataClass [`.query()`](DataClassClass.md#query) page.
+
+#### See also
+
+[`.query()`](DataClassClass.md#query) for dataclass
+
+
+
+
+
+
+## .queryPath
+
+
+
+**.queryPath** : string
+
+
+#### Description
+
+The `.queryPath` property contains a detailed description of the query as it was actually performed by Qodly. This property is available for `EntitySelection` objects generated through queries if the `"queryPath":true` property was passed in the *querySettings* parameter of the [`.query()`](#query) function.
+
+For more information, refer to the **querySettings parameter** paragraph in the Dataclass[`.query()`](DataClassClass.md#query) page.
+
+
+
+
+
+
+## .queryPlan
+
+
+
+**.queryPlan** : string
+
+
+#### Description
+
+The `.queryPlan` property contains a detailed description of the query just before it is executed (i.e., the planned query). This property is available for `EntitySelection` objects generated through queries if the `"queryPlan":true` property was passed in the *querySettings* parameter of the [`.query()`](#query) function.
+
+For more information, refer to the **querySettings parameter** paragraph in the Dataclass[`.query()`](DataClassClass.md#query) page.
+
+
+
+
+
+## .selected()
+
+
+
+**.selected**( *selectedEntities* : 4D.EntitySelection ) : object
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|selectedEntities |4D.EntitySelection|→|Entity selection with entities for which to know the rank in the entity selection|
+|Result|object|←|Range(s) of selected entities in entity selection|
+
+
+#### Description
+
+The `.selected()` function returns an object describing the position(s) of *selectedEntities* in the original entity selection.
+
+>This function does not modify the original entity selection.
+
+Pass in the *selectedEntities* parameter an entity selection containing entities for which you want to know the position in the original entity selection. *selectedEntities* must be an entity selection belonging to the same dataclass as the original entity selection, otherwise an error 1587 - "The entity selection comes from an incompatible dataclass" is raised.
+
+#### Result
+
+The returned object contains the following properties:
+
+|Property|Type|Description
+|---|---|---|
+|ranges|collection|collection of range objects|
+|ranges[].start|integer|First entity index in the range|
+|ranges[].end|integer|Last entity index in the range|
+
+If a `ranges` property contains a single entity, `start = `end`. Index starts at 0.
+
+
+The function returns an empty collection in the `ranges` property if the original entity selection or the *selectedEntities* entity selection is empty.
+
+#### Example
+
+```qs
+var invoices, cashSel, creditSel : cs.InvoicesSelection
+var result1, result2 : object
+
+invoices = ds.Invoices.all()
+
+cashSel = ds.Invoices.query("payment = :1", "Cash")
+creditSel = ds.Invoices.query("payment IN :1", newCollection("Cash", "Credit Card"))
+
+result1 = invoices.selected(cashSel)
+result2 = invoices.selected(creditSel)
+
+//result1 = {ranges:[{start:0;end:0},{start:3;end:3},{start:6;end:6}]}
+//result2 = {ranges:[{start:0;end:1},{start:3;end:4},{start:6;end:7}]}
+
+```
+
+
+
+
+
+
+
+
+
+
+## .slice()
+
+
+**.slice**( *startFrom* : integer \{ , *end* : integer \} ) : 4D.EntitySelection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|startFrom |integer |→|Index to start the operation at (included) |
+|end |integer|→|End index (not included)|
+|Result|4D.EntitySelection|←|New entity selection containing sliced entities (shallow copy)|
+
+
+#### Description
+
+The `.slice()` function returns a portion of an entity selection into a new entity selection, selected from the *startFrom* index to the *end* index (*end* is not included) or to the last entity of the entity selection. This method returns a shallow copy of the entity selection (it uses the same entity references).
+
+>This function does not modify the original entity selection.
+
+The returned entity selection contains the entities specified by *startFrom* and all subsequent entities up to, but not including, the entity specified by *end*. If only the *startFrom* parameter is specified, the returned entity selection contains all entities from *startFrom* to the last entity of the original entity selection.
+
+* If *startFrom* < 0, it is recalculated as *startFrom: = startFrom+length* (it is considered as the offset from the end of the entity selection). If the calculated value < 0, *startFrom* is set to 0.
+* If *startFrom >= length*, the function returns an empty entity selection.
+* If *end* < 0, it is recalculated as *end: = end+length*.
+* If *end < startFrom* (passed or calculated values), the method does nothing.
+
+If the entity selection contains entities that were dropped in the meantime, they are also returned.
+
+#### Example 1
+
+You want to get a selection of the first 9 entities of the entity selection:
+
+```qs
+var sel, sliced : cs.EmployeeSelection
+sel = ds.Employee.query("salary > :1",50000)
+sliced = sel.slice(0,9) //
+```
+
+
+#### Example 2
+
+Assuming we have ds.Employee.all().length = 10
+
+```qs
+var slice : cs.EmployeeSelection
+slice = ds.Employee.all().slice(-1,-2) //tries to return entities from index 9 to 8,
+ //but since 9 > 8, returns an empty entity selection
+
+```
+
+
+
+
+
+## .sum()
+
+
+**.sum**( *attributePath* : string ) : number
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|attributePath |string|→|Path of the attribute to be used for calculation|
+|Result|number|←|Sum of entity selection values|
+
+
+#### Description
+
+
+The `.sum()` function returns the sum for all *attributePath* values in the entity selection.
+
+`.sum()` returns 0 if the entity selection is empty.
+
+The sum can only be done on values of number type. If the *attributePath* is an object property, only numerical values are taken into account for the calculation (other value types are ignored). In this case, if *attributePath* leads to a property that does not exist in the object or does not contain any numeric values, `.sum()` returns 0.
+
+An error is returned if:
+
+* *attributePath* is not a numerical or an object attribute,
+* *attributePath* is a related attribute,
+* *attributePath* is not found in the entity selection dataclass.
+
+
+
+#### Example
+
+```qs
+var sel : cs.EmployeeSelection
+var sum : number
+
+sel = ds.Employee.query("salary < :1",20000)
+sum = sel.sum("salary")
+```
+
+
+
+
+
+## .toCollection( )
+
+
+
+**.toCollection**( \{ *options* : integer \{ , *begin* : integer \{ , *howMany* : integer \} } ) : collection **.toCollection**( *filterString* : string \{, *options* : integer \{ , *begin* : integer \{ , *howMany* : integer \}}} ) : collection **.toCollection**( *filterCol* : collection \{, *options* : integer \{ , *begin* : integer \{ , *howMany* : integer \}}} ) : collection
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|filterString |string|→|string with entity attribute path(s) to extract|
+|filterCol |collection|→|collection of entity attribute path(s) to extract|
+|options|integer|→|`kWithPrimaryKey`: adds the primary key `kWithStamp`: adds the stamp|
+|begin|integer| →|Designates the starting index|
+|howMany|integer|→|Number of entities to extract|
+|Result|collection|←|collection of objects containing attributes and values of entity selection|
+
+
+#### Description
+
+The `.toCollection()` function creates and returns a collection where each element is an object containing a set of properties and values corresponding to the attribute names and values for the entity selection.
+
+If no filter parameter is passed or the first parameter contains an empty string or `*`, all the attributes are extracted. Attributes with [kind](DataClassClass.md#attributename) property as "relatedEntity" are extracted with the simple form: an object with property `\_\_KEY` (primary key). Attributes with kind property as "relatedEntities" are not extracted.
+
+Or, you can designate the entity attributes to extract using a filter parameter. You can use one of these two filters:
+
+* *filterString* --a string with property paths separated with commas: "propertyPath1, propertyPath2, ...".
+* *filterCol* --a collection of strings containing property paths: \["propertyPath1","propertyPath2",...]
+
+
+If a filter is specified for an attribute of the `relatedEntity` kind:
+
+* propertyPath = "relatedEntity" -> it is extracted with simple form
+* propertyPath = "relatedEntity.*" -> all the properties are extracted
+* propertyPath = "relatedEntity.propertyName1, relatedEntity.propertyName2, ..." -> only those properties are extracted
+
+
+If a filter is specified for an attribute of the `relatedEntities` kind:
+
+* propertyPath = "relatedEntities.*" -> all the properties are extracted
+* propertyPath = "relatedEntities.propertyName1, relatedEntities.propertyName2, ..." -> only those properties are extracted
+
+
+
+In the *options* parameter, you can pass the `kWithPrimaryKey` and/or `kWithStamp` selector(s) to add the entity's primary keys and/or stamps in extracted objects.
+
+The *begin* parameter allows you to indicate the starting index of the entities to extract. You can pass any value between 0 and entity selection length-1.
+
+The *howMany* parameter lets you specify the number of entities to extract, starting with the one specified in *begin*. Dropped entities are not returned but are taken into account according to *howMany*. For example, if *howMany* = 3 and there is 1 dropped entity, only 2 entities are extracted.
+
+If *howMany* > length of the entity selection, the method returns (length - *begin*) objects.
+
+An empty collection is returned if:
+
+* the entity selection is empty, or
+* *begin* is greater than the length of the entity selection.
+
+
+#### Example 1
+
+The following structure will be used throughout all examples of this section:
+
+
+
+
+Example without filter or options parameter:
+
+```qs
+ var employeesCollection : collection
+ var employees : cs.EmployeeSelection
+
+ employeesCollection = newCollection
+ employees = ds.Employee.all()
+ employeesCollection = employees.toCollection()
+```
+
+Returns:
+
+```json
+[
+ {
+ "ID": 416,
+ "firstName": "Gregg",
+ "lastName": "Wahl",
+ "salary": 79100,
+ "birthDate": "1963-02-01T00:00:00.000Z",
+ "woman": false,
+ "managerID": 412,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 412
+
+ }
+ },
+ {
+ "ID": 417,
+ "firstName": "Irma",
+ "lastName": "Durham",
+ "salary": 47000,
+ "birthDate": "1992-06-16T00:00:00.000Z",
+ "woman": true,
+ "managerID": 412,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 412
+ }
+ }
+]
+```
+
+#### Example 2
+
+Example with options:
+
+```qs
+var employeesCollection : collection
+var employees : cs.EmployeeSelection
+
+employeesCollection = newCollection
+employees = ds.Employee.all()
+employeesCollection = employees.toCollection("",kWithPrimaryKey+kWithStamp)
+```
+
+Returns:
+
+```json
+[
+ {
+ "__KEY": 416,
+ "__STAMP": 1,
+ "ID": 416,
+ "firstName": "Gregg",
+ "lastName": "Wahl",
+ "salary": 79100,
+ "birthDate": "1963-02-01T00:00:00.000Z",
+ "woman": false,
+ "directReports": 412,
+ "employer": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 412
+ }
+ },
+ {
+ "__KEY": 417,
+ "__STAMP": 1,
+ "ID": 417,
+ "firstName": "Irma",
+ "lastName": "Durham",
+ "salary": 47000,
+ "birthDate": "1992-06-16T00:00:00.000Z",
+ "woman": true,
+ "directReports": 412,
+ "employerID": 20,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 20
+ },
+ "manager": {
+ "__KEY": 412
+ }
+ }]
+```
+
+#### Example 3
+
+Example with slicing and filtering on properties:
+
+```qs
+var employeesCollection, filter : collection
+var employees : cs.EmployeeSelection
+
+employeesCollection = newCollection
+filter = newCollection
+filter.push("firstName")
+filter.push("lastName")
+
+employees = ds.Employee.all()
+employeesCollection = employees.toCollection(filter,0,0,2)
+```
+
+Returns:
+
+```json
+[
+ {
+ "firstName": "Gregg",
+ "lastName": "Wahl"
+ },
+ {
+ "firstName": "Irma",
+ "lastName": "Durham"
+ }
+]
+
+```
+
+
+#### Example 4
+
+Example with `relatedEntity` type with simple form:
+
+
+```qs
+var employeesCollection : collection
+employeesCollection = newCollection
+employeesCollection = employees.toCollection("firstName,lastName,employer")
+```
+
+returns:
+
+```json
+[
+ {
+ "firstName": "Gregg",
+ "lastName": "Wahl",
+ "employer": {
+ "__KEY": 20
+ }
+ },
+ {
+ "firstName": "Irma",
+ "lastName": "Durham",
+ "employer": {
+ "__KEY": 20
+ }
+ },
+ {
+ "firstName": "Lorena",
+ "lastName": "Boothe",
+ "employer": {
+ "__KEY": 20
+ }
+ }
+ ]
+```
+
+#### Example 5
+
+Example with *filterCol* parameter:
+
+```qs
+var employeesCollection, coll : collection
+employeesCollection = newCollection
+coll = newCollection("firstName","lastName")
+employeesCollection = employees.toCollection(coll)
+```
+
+Returns:
+
+```json
+[
+ {
+ "firstName": "Joanna",
+ "lastName": "Cabrera"
+ },
+ {
+ "firstName": "Alexandra",
+ "lastName": "Coleman"
+ }
+]
+```
+
+#### Example 6
+
+Example with extraction of all properties of a relatedEntity:
+
+```qs
+var employeesCollection, coll : collection
+employeesCollection = newCollection
+coll = newCollection
+coll.push("firstName")
+coll.push("lastName")
+coll.push("employer.*")
+employeesCollection = employees.toCollection(coll)
+```
+
+Returns:
+
+```json
+[
+ {
+ "firstName": "Gregg",
+ "lastName": "Wahl",
+ "employer": {
+ "ID": 20,
+ "name": "India Astral Secretary",
+ "creationDate": "1984-08-25T00:00:00.000Z",
+ "revenues": 12000000,
+ "extra": null
+ }
+ },
+ {
+ "firstName": "Irma",
+ "lastName": "Durham",
+ "employer": {
+ "ID": 20,
+ "name": "India Astral Secretary",
+ "creationDate": "1984-08-25T00:00:00.000Z",
+ "revenues": 12000000,
+ "extra": null
+ }
+ },
+ {
+ "firstName": "Lorena",
+ "lastName": "Boothe",
+ "employer": {
+ "ID": 20,
+ "name": "India Astral Secretary",
+ "creationDate": "1984-08-25T00:00:00.000Z",
+ "revenues": 12000000,
+ "extra": null
+ }
+ }
+ ]
+```
+
+#### Example 7
+
+Example with extraction of some properties of a relatedEntity:
+
+```qs
+var employeesCollection : collection
+employeesCollection = newCollection
+employeesCollection = employees.toCollection("firstName, lastName, employer.name")
+```
+
+```json
+[
+ {
+ "firstName": "Gregg",
+ "lastName": "Wahl",
+
+ "employer": {
+ "name": "India Astral Secretary"
+ }
+ },
+ {
+ "firstName": "Irma",
+ "lastName": "Durham",
+ "employer": {
+ "name": "India Astral Secretary"
+ }
+ },
+ {
+ "firstName": "Lorena",
+ "lastName": "Boothe",
+ "employer": {
+ "name": "India Astral Secretary"
+ }
+ }]
+```
+
+#### Example 8
+
+Example with extraction of some properties of `relatedEntities`:
+
+```qs
+ var employeesCollection : collection
+ employeesCollection = newCollection
+ employeesCollection = employees.toCollection("firstName, lastName, directReports.firstName")
+```
+
+Returns:
+
+```json
+[
+ {
+ "firstName": "Gregg",
+ "lastName": "Wahl",
+ "directReports": []
+ },
+ {
+ "firstName": "Mike",
+ "lastName": "Phan",
+ "directReports": [
+ {
+ "firstName": "Gary"
+ },
+ {
+ "firstName": "Sadie"
+ },
+ {
+ "firstName": "Christie"
+ }
+ ]
+ },
+ {
+ "firstName": "Gary",
+
+ "lastName": "Reichert",
+ "directReports": [
+ {
+ "firstName": "Rex"
+ },
+ {
+ "firstName": "Jenny"
+ },
+ {
+ "firstName": "Lowell"
+ }
+ ]
+ }]
+```
+
+#### Example 9
+
+Example with extraction of all properties of `relatedEntities`:
+
+```qs
+var employeesCollection : collection
+employeesCollection = newCollection
+employeesCollection = employees.toCollection("firstName, lastName, directReports.*")
+
+```
+
+```json
+[
+ {
+ "firstName": "Gregg",
+ "lastName": "Wahl",
+ "directReports": []
+ },
+ {
+ "firstName": "Mike",
+ "lastName": "Phan",
+ "directReports": [
+ {
+ "ID": 425,
+ "firstName": "Gary",
+ "lastName": "Reichert",
+ "salary": 65800,
+ "birthDate": "1957-12-23T00:00:00.000Z",
+ "woman": false,
+ "managerID": 424,
+ "employerID": 21,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 21
+ },
+ "manager": {
+ "__KEY": 424
+ }
+ },
+ {
+ "ID": 426,
+ "firstName": "Sadie",
+ "lastName": "Gallant",
+ "salary": 35200,
+ "birthDate": "2022-01-03T00:00:00.000Z",
+ "woman": true,
+ "managerID": 424,
+ "employerID": 21,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 21
+ },
+ "manager": {
+ "__KEY": 424
+ }
+ }
+ ]
+ },
+ {
+ "firstName": "Gary",
+ "lastName": "Reichert",
+ "directReports": [
+ {
+ "ID": 428,
+ "firstName": "Rex",
+ "lastName": "Chance",
+ "salary": 71600,
+ "birthDate": "1968-08-09T00:00:00.000Z",
+ "woman": false,
+
+ "managerID": 425,
+ "employerID": 21,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 21
+ },
+ "manager": {
+ "__KEY": 425
+ }
+ },
+ {
+ "ID": 429,
+ "firstName": "Jenny",
+ "lastName": "Parks",
+ "salary": 51300,
+ "birthDate": "1984-05-25T00:00:00.000Z",
+ "woman": true,
+ "managerID": 425,
+ "employerID": 21,
+ "photo": "[object Picture]",
+ "extra": null,
+ "employer": {
+ "__KEY": 21
+ },
+ "manager": {
+ "__KEY": 425
+ }
+ }
+ ]
+ }
+]
+```
+
+
+
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/FileClass.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/FileClass.md
new file mode 100644
index 000000000..fb51974cc
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/FileClass.md
@@ -0,0 +1,441 @@
+---
+id: FileClass
+title: File
+---
+
+`File` objects are created with the [`file`](commands/file) command. They contain references to disk files that may or may not actually exist on disk. For example, when you execute the `file` command to create a new file, a valid `file` object is created but nothing is actually stored on disk until you call the [`file.create()`](#create) function.
+
+### Example
+
+The following example creates a preferences file in the project folder:
+
+```code4d
+var created : boolean
+created = file("/PACKAGE/SpecialPrefs/"+storage.users[2].name+".myPrefs").create()
+```
+
+### Pathnames
+
+`File` objects support several pathnames, including `filesystems` or `posix` syntax. Supported pathnames are detailed in the [**Pathnames**](basics/lang-pathnames.md) page.
+
+
+### Functions and properties
+
+||
+|---|
+|[](#copyto) |
+|[](#create) |
+|[](#createalias) |
+|[](#delete) |
+|[](#exists) |
+|[](#extension) |
+|[](#fullname) |
+|[](#getcontent)|
+|[](#gettext) |
+|[](#hidden) |
+|[](#isalias) |
+|[](#isfile) |
+|[](#isfolder) |
+|[](#iswritable) |
+|[](#modificationdate) |
+|[](#modificationtime) |
+|[](#moveto) |
+|[](#name) |
+|[](#open) |
+|[](#original) |
+|[](#parent) |
+|[](#path) |
+|[](#rename) |
+|[](#setcontent) |
+|[](#settext) |
+|[](#size) |
+
+
+
+
+## 4D.File.new()
+
+
+
+**4D.File.new** ( *path* : string ) : 4D.File
+
+#### Description
+
+The `4D.File.new()` function creates and returns a new object of the `4D.File` type. It is identical to the [`file`](commands/file.md) command (shortcut).
+
+> It is recommended to use the [`file`](commands/file.md) shortcut command instead of `4D.File.new()`.
+
+
+
+
+## .create()
+
+
+
+**Not available for ZIP archives**
+
+
+**.create**() : boolean
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|Result|boolean|←|true if the file was created successfully, false otherwise|
+
+#### Description
+
+The `.create()` function creates a file on disk according to the properties of the `file` object.
+
+if necessary, the function creates the folder hierachy as described in the [path](#path) property. if the file already exists on disk, the function does nothing (no error is thrown) and returns false.
+
+**Returned value**
+
+* **true** if the file is created successfully,
+* **false** if a file with the same name already exists or if an error occured.
+
+#### Example
+
+Creation of a preferences file in the project folder:
+
+```qs
+ var created : boolean
+ created = File("/PACKAGE/SpecialPrefs/settings.myPrefs").create()
+```
+
+
+
+## .createAlias()
+
+
+**.createAlias**( *destinationFolder* : 4D.Folder , *aliasName* : string ) : 4D.File
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|destinationFolder|4D.Folder|→|Destination folder for the alias or shortcut|
+|aliasName|string|→|Name of the alias or shortcut|
+|Result|4D.File|←|Alias or shortcut file reference|
+
+#### Description
+
+The `.createAlias()` function creates a symbolic link to the file with the specified *aliasName* name in the folder designated by the *destinationFolder* object.
+
+Pass the name of the symbolic link in the *aliasName* parameter.
+
+**Returned object**
+
+A `4D.File` object with the `isAlias` property set to **true**.
+
+#### Example
+
+You want to create a symbolic link to a file in your resources folder:
+
+```qs
+ myFile = file("/SOURCES/Shared/Archives/ReadMe.txt")
+ aliasFile = myFile.createAlias(file("/RESOURCES"),"ReadMe")
+```
+
+
+
+
+## .delete()
+
+**.delete**()
+
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+| | ||Does not require any parameters|
+
+#### Description
+
+
+
+
+
+
+The `.delete()` function deletes the file.
+
+if the file does not exist on disk, the function does nothing (no error is generated).
+
+>**WARNING**: `.delete()` can delete any file on a disk. This includes documents created with other applications, as well as the applications themselves. `.delete()` should be used with extreme caution. Deleting a file is a permanent operation and cannot be undone.
+
+#### Example
+
+You want to delete a specific file in the project folder:
+
+```qs
+ var tempo : 4D.File
+ var info : string
+ tempo = file("PACKAGE/SpecialPrefs/settings.prefs")
+ if(tempo.exists)
+ tempo.delete()
+ info = "User preference file deleted."
+ end
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## .moveTo()
+
+
+
+**.moveTo**( *destinationFolder* : 4D.Folder \{ , *newName* : string \} ) : 4D.File
+
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|destinationFolder|4D.Folder|→|Destination folder|
+|newName|string|→|Full name for the moved file|
+|Result|4D.File|←|Moved file|
+
+
+#### Description
+
+The `.moveTo()` function moves or renames the `file` object into the specified *destinationFolder*.
+
+The *destinationFolder* must exist on disk, otherwise an error is generated.
+
+
+By default, the file retains its name when moved. if you want to rename the moved file, pass the new full name in the *newName* parameter. The new name must comply with naming rules (e.g., it must not contain characters such as ":", "/", etc.), otherwise an error is returned.
+
+**Returned object**
+
+The moved `file` object.
+
+#### Example
+
+```qs
+myFolder = folder("/SOURCES/Shared/Contents")
+myFile = myFolder.file("Infos.txt")
+myFile.moveTo(myFolder.folder("Archives"),"Infos_old.txt")
+```
+
+
+
+
+
+## .open()
+
+
+**.open**( \{ *mode* : string \} ) : 4D.FileHandle **.open**( \{ *options* : object \} ) : 4D.FileHandle
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|mode|string|→|Opening mode: "read", "write", "append"|
+|options|object|→|Opening options|
+|Result|[4D.FileHandle](FileHandleClass)|←|New File handle object|
+
+#### Description
+
+The `.open()` function creates and returns a new [4D.FileHandle](FileHandleClass) object on the file, in the specified *mode* or with the specified *options*. You can use functions and properties of the [4D.FileHandle](FileHandleClass) class to write, read, or append contents to the file.
+
+if you use the *mode* (string) parameter, pass the opening mode for the file handle:
+
+
+
+|*mode*|Description|
+|---|---|
+|"read"|(Default) Creates a file handle to read values from the file. if the file does not exist on disk, an error is returned. You can open as many file handles as you want in "read" mode on the same file object.|
+|"write"|Creates a file handle to write values to the file (starting at the beginning of the file content). if the file does not exist on disk, it is created. You can open only one file handle in "write" mode on the same file object.|
+|"append"|Creates a file handle to write values to the file (starting at the end of the file content). if the file does not exist on disk, it is created. You can open only one file handle in "append" mode on the same file object.|
+
+> The *mode* value is case sensitive.
+
+If you use the *options* (object) parameter, you can pass more options for the file handle through the following properties (these properties can be read afterwards from the opened [file handle object](FileHandleClass)):
+
+|*options*|Type|Description|Default|
+|---|---|---|---|
+|`.mode`|string|Opening mode (see *mode* above)|"read"|
+|`.charset`|string|Charset used when reading from or writing to the file. Use the standard name of the set (for example "ISO-8859-1" or "UTF-8")|"UTF-8"|
+|`.breakModeRead`|string or number|Processing mode for line breaks used when reading in the file (see below)|"native" or 1|
+|`.breakModeWrite`|string or number|Processing mode for line breaks used when writing to the file (see below)|"native" or 1|
+
+This function replaces all original end-of-line delimiters. The `.breakModeRead` and `.breakModeWrite` indicate the processing to apply to end-of-line characters in the document. You can use one of the following values (string or number):
+
+|Break mode as text|Break mode as number (constant)|Description|
+|---|---|---|
+|"native"|1 (`kDocumentWithNativeFormat`)|(Default) Line breaks are converted to the native format of the operating system: LF (line feed) under Unix and macOS, CRLF (carriage return + line feed) under Windows|
+|"crlf"|2 (`kDocumentWithCRLF`)|Line breaks are converted to CRLF (carriage return + line feed), the default Windows format|
+|"cr"|3 (`kDocumentWithCR`)|Line breaks are converted to CR (carriage return), the default Classic Mac OS format|
+|"lf"|4 (`kDocumentWithLF`)|Line breaks are converted to LF (line feed), the default Unix and macOS format|
+
+> The *Break mode as text* value is case sensitive.
+
+#### Example
+
+You want to create a file handle for reading the "ReadMe.txt" file:
+
+```qs
+var f : 4D.File
+var fhandle : 4D.FileHandle
+
+f = file("/SOURCES/ReadMe.txt")
+fhandle = f.open("read")
+
+```
+
+
+
+
+
+
+
+
+
+## .rename()
+
+
+**.rename**( *newName* : string ) : 4D.File
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|newName|string|→|New full name for the file|
+|Result|4D.File|←|Renamed file|
+
+#### Description
+
+The `.rename()` function renames the file with the name you passed in *newName* and returns the renamed `file` object.
+
+The *newName* parameter must comply with naming rules (e.g., it must not contain characters such as ":", "/", etc.), otherwise an error is returned. if a file with the same name already exists, an error is returned.
+
+Note that the function modifies the full name of the file, i.e. if you do not pass an extension in *newName*, the file will have a name without an extension.
+
+**Returned object**
+
+The renamed `file` object.
+
+#### Example
+
+You want to rename "ReadMe.txt" in "ReadMe_new.txt":
+
+```qs
+ toRename = file("/SOURCES/ReadMe.txt")
+ newName = toRename.rename(toRename.name+"_new"+toRename.extension)
+```
+
+
+
+
+## .setContent()
+
+
+**.setContent** ( *content* : blob )
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|content|blob|→|New contents for the file|
+
+#### Description
+
+The `.setContent()` function rewrites the entire content of the file using the data stored in the *content* blob. For information on blobs, please refer to the [Blob](basics/lang-blob.md) section.
+
+#### Example
+
+```qs
+ var myFile : 4D.File
+ var vEntity : cs.myClassEntity
+
+ myFile = "/SOURCES/Archives/data.txt")
+ vEntity = ds.myClass.all().first() //get an entity
+ myFile.setContent(vEntity.infoBlob)
+ vEntity.save()
+
+```
+
+
+
+## .setText()
+
+
+**.setText** ( *text* : string \{, *charSetName* : string \{ , *breakMode* : integer \} } ) **.setText** ( *text* : string \{, *charSetNum* : integer \{ , *breakMode* : integer \} } )
+
+
+
+
+|Parameter|Type||Description|
+|---------|----|---|--------|
+|text|string|→|string to store in the file|
+|charSetName|string|→|Name of character set|
+|charSetNum|integer|→|Number of character set|
+|breakMode|integer|→|Processing mode for line breaks|
+
+
+#### Description
+
+The `.setText()` function writes *text* as the new contents of the file.
+
+if the file referenced in the `file` object does not exist on the disk, it is created by the function. When the file already exists on the disk, its prior contents are erased, except if it is already open, in which case, its contents are locked and an error is generated.
+
+In *text*, pass the text to write to the file. It can be a literal ("my text"), or a 4D text field or variable.
+
+Optionally, you can designate the character set to be used for writing the contents. You can pass either:
+
+* in *charSetName*, a string containing the standard set name (for example "ISO-8859-1" or "UTF-8"),
+* or in *charSetNum*, the MIBEnum ID (number) of the standard set name.
+
+> For the list of character sets supported by Qodly, refer to the description of the `convertFromText` command.
+
+if a Byte Order Mark (BOM) exists for the character set, Qoldy inserts it into the file unless the character set used contains the suffix "-no-bom" (e.g. "UTF-8-no-bom"). if you do not specify a character set, by default 4D uses the "UTF-8" character set without BOM.
+
+In *breakMode*, you can pass a number indicating the processing to apply to end-of-line characters before saving them in the file. The following constants are available:
+
+|Constant|Value|Comment|
+|--------|-----|-------|
+|`kDocumentUnchanged`|0|No processing|
+|`kDocumentWithNativeFormat`|1|(Default) Line breaks are converted to the native format of the operating system (line feed on Unix)|
+|`kDocumentWithCRLF`|2|Line breaks are converted to CRLF (carriage return + line feed), the default Windows format|
+|`kDocumentWithCR`|3|Line breaks are converted to CR (carriage return), the default Classic Mac OS format|
+|`kDocumentWithLF`|4|Line breaks are converted to LF (line feed), the default Unix and macOS format|
+
+By default, when you omit the *breakMode* parameter, line breaks are processed in native mode (1).
+
+
+#### Example
+
+```qs
+var myFile : 4D.File
+myFile = file("/SOURCES/Hello.txt")
+myFile.setText("Hello world")
+```
+
+
+
+
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/FileHandleClass.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/FileHandleClass.md
new file mode 100644
index 000000000..b3fa93d70
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/FileHandleClass.md
@@ -0,0 +1,471 @@
+---
+id: FileHandleClass
+title: FileHandle
+---
+
+The `FileHandle` class has functions that allow you to sequentially read from or append contents to an opened [`file`](FileClass.md) object. A file handle can access any part of a document.
+
+File handle objects are created with the [`file.open()`](FileClass.md#open) function.
+
+:::tip
+
+To read or write a whole document at once, you might consider using the [file.getText()](FileClass.md#gettext) and [file.setText()](FileClass.md#settext) functions.
+
+:::
+
+Thanks to the Qodly object *refcounting*, a file handle is automatically deleted when it is no longer referenced and thus, the requested [`file`](FileClass.md) object is automatically closed. Consequently, with file handles you don't need to worry about closing documents.
+
+
+### Example
+
+```qs
+var f : 4D.File
+var fhandle : 4D.FileHandle
+f = file("/PACKAGE/example.txt")
+
+//Writing line by line from the start
+fhandle = f.open("write")
+text = "Hello World"
+for (line, 1, 4)
+ fhandle.writeLine(text+string(line))
+end
+
+//Writing line by line from the end
+fhandle = f.open("append")
+text = "Hello New World!"
+for (line, 1, 4)
+ fhandle.writeLine(text+string(line))
+end
+
+//Reading using a stop character and an object parameter
+o = newObject()
+o.mode = "read"
+o.charset = "UTF-8"
+o.breakModeRead = Document with CRLF
+stopChar = "!"
+fhandle = f.open(o)
+text = fhandle.readText(stopChar)
+
+//Reading line by line
+lines = newCollection()
+fhandle = f.open("read")
+while (Not(fhandle.eof))
+ lines.push(fhandle.readLine())
+end
+
+```
+
+### Functions and properties
+
+File handle objects cannot be shared.
+
+||
+|---|
+|[](#breakmoderead) |
+|[](#breakmodewrite) |
+|[](#charset) |
+|[](#eof) |
+|[](./commands/file) |
+|[](#getsize) |
+|[](#mode) |
+|[](#offset) |
+|[](#readblob) |
+|[](#readline) |
+|[](#readtext) |
+|[](#setsize) |
+|[](#writeblob) |
+|[](#writeline) |
+|[](#writetext) |
+
+
+
+
+## .breakModeRead
+
+**.breakModeRead** : string
+
+
+#### Description
+
+The `.breakModeRead` property returns the processing mode for line breaks used when reading the file.
+
+
+The `.breakModeRead` property can be defined at the handle creation with the [`file.open()`](FileClass.md#open) function (see [the `.open()` function](FileClass.md#open) for more information). Default is "native".
+
+> The `.breakModeRead` property always contains a text value, even if the `.open()` option was set using a number (constant).
+
+
+This property is **read-only**.
+
+
+
+
+
+## .breakModeWrite
+
+
+**.breakModeWrite** : string
+
+
+#### Description
+
+The `.breakModeWrite` property returns the processing mode for line breaks used when writing to the file.
+
+The `.breakModeWrite` property can be defined at the handle creation with the [`file.open()`](FileClass.md#open) function (see [the `.open()` function](FileClass.md#open) for more information). Default is "native".
+
+> The `.breakModeWrite` property always contains a text value, even if the `.open()` option was set using a number (constant).
+
+
+This property is **read-only**.
+
+
+
+
+
+
+## .charset
+
+
+**.charset** : string
+
+
+#### Description
+
+The `.charset` property returns the charset used when reading from or writing to the file.
+
+The charset can be defined at the handle creation with the [`file.open()`](FileClass.md#open) function. Default is "UTF-8".
+
+This property is **read-only**.
+
+
+
+
+
+## .eof
+
+**.eof** : boolean
+
+
+#### Description
+
+The `.eof` property returns True is the `offset` has reached the end of the file, and False otherwise.
+
+This property is **read-only**.
+
+
+
+
+
+## .file
+
+**.file** : 4D.File
+
+
+#### Description
+
+The `.file` property returns the [4D.File](FileClass.md) object on which the handle has been created.
+
+This property is **read-only**.
+
+
+
+
+
+## .getSize()
+
+
+**.getSize()** : number
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|Result|number|←|Size of the document in bytes|
+
+#### Description
+
+The `.getSize()` function returns the current size of the document, expressed in bytes.
+
+> This function returns the same value as the ([.size](FileClass.md#size)) property of the `file` class.
+
+#### See also
+
+[.setSize()](#setsize), [file.size](FileClass.md#size)
+
+
+
+
+
+## .mode
+
+
+**.mode** : string
+
+
+#### Description
+
+The `.mode` property returns the mode in which the file handle was created: "read", "write", or "append".
+
+The mode can be defined at the handle creation with the [`file.open()`](FileClass.md#open) function. Default is "read".
+
+This property is **read-only**.
+
+
+
+
+
+
+## .offset
+
+
+**.offset** : number
+
+
+#### Description
+
+The `.offset` property returns the current offset of the data stream (position inside the document). The offset value is automatically updated after read and write operations.
+
+Setting the `.offset` will change its current value.
+
+- If the passed value is negative, the `.offset` is set to the start of the file (zero).
+- If the passed value is higher than the size of the file, the `.offset` is set to the end of the file (size of file).
+
+This property is **read/write**.
+
+
+:::caution
+
+When a file handle is created, the `.offset` value is a number of bytes. However, the unit of offset measurement differs according to the reading function: with [`readBlob()`](#readblob), `.offset` is a number of bytes, whereas with [`readText()`](#readtext)/[`readLine()`](#readline) it is a number of characters. Depending on the file's character set, a character corresponds to one or more bytes. So, if you start reading with `readBlob()` and then call `readText()`, text reading will start at an inconsistent position. It is therefore essential to set the `.offset` property yourself if you switch from reading/writing blob to reading/writing text in the same filehandle. For example:
+
+```4d
+ // Open a european text file using utf-16 encoding (two bytes per character)
+ // We want to read the first 10 characters as bytes, then the remaining as text.
+var vFileHandle : 4D.File
+var vBlob : 4D.Blob
+var vString : string
+vFileHandle = file("/SOURCES/Shared/sample_utf_16.txt").open()
+ // read the 20 first bytes (i.e. 10 characters)
+vBlob = vFileHandle.readBlob(20) // vFileHandle.offset=20
+ // then read all text skipping the first 10 characters we just read in previous blob
+ // because we are now reading text instead of bytes, the meaning of 'offset' is not the same.
+ // We need to translate it from bytes to characters.
+vFileHandle.offset = 10 // ask to skip 10 utf-16 characters (20 bytes)
+vString = vFileHandle.readText()
+```
+
+:::
+
+
+
+
+
+
+
+## .readBlob()
+
+**.readBlob**( *bytes* : number ) : [4D.Blob](BlobClass)
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|*bytes*|number|→|Number of bytes to be read|
+|Result|[4D.Blob](BlobClass)|←|Bytes read from the file|
+
+
+
+#### Description
+
+The `.readBlob()` function returns a blob a *bytes* size from the file, starting from the current position .
+
+When this function is executed, the current position ([.offset](#offset)) is updated after the last byte read.
+
+#### See also
+
+[.writeBlob()](#writeblob)
+
+
+
+
+
+
+
+## .readLine()
+
+
+**.readLine()** : string
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|Result|string|←|Line of text|
+
+
+
+#### Description
+
+The `.readLine()` function returns a line of text from the current position until an end-of-line delimiter is encountered or the end of the document is reached.
+
+When this function is executed, the current position ([.offset](#offset)) is updated.
+
+:::caution Warning
+
+This function assumes that the [`.offset`](#offset) property is a number of characters, not a number of bytes. For more information, see the [.offset description](#offset).
+
+:::
+
+
+> When this function is executed for the first time on a file handle, the whole document contents is loaded in a buffer.
+
+
+#### See also
+
+[.readText()](#readtext), [.writeLine()](#writeline)
+
+
+
+
+
+## .readText()
+
+
+**.readText**( \{ *stopChar* : string \} ) : string
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|*stopChar*|string|→|Character(s) at which to stop reading|
+|Result|string|←|string from the file|
+
+
+#### Description
+
+The `.readText()` function returns text from the file, starting from the current position until the first *stopChar* string is encountered (if passed) or the end of file is reached.
+
+The *stopChar* character string is not included in the returned text. If you omit the *stopChar* parameter, the whole document text is returned.
+
+When this function is executed, the ([.offset](#offset)) is placed just after the *stopChar* string.
+
+:::caution Warning
+
+This function assumes that the [`.offset`](#offset) property is a number of characters, not a number of bytes. For more information, see the [.offset description](#offset).
+
+:::
+
+If the *stopChar* parameter is passed and not found, `.readText()` returns an empty string and the [.offset](#offset) is left untouched.
+
+> When this function is executed for the first time on a file handle, the whole document contents is loaded in a buffer.
+
+#### See also
+
+[.readLine()](#readline), [.writeText()](#writetext)
+
+
+
+
+
+## .setSize()
+
+
+**.setSize**( *size* : number )
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|size|number|→|New size of the document in bytes|
+
+
+#### Description
+
+The `.setSize()` function sets a new *size* in bytes for the document.
+
+If the *size* value is less than the current document size, the document content is truncated from the beginning to get the new *size* .
+
+#### See also
+
+[.getSize()](#getsize), [file.size](FileClass.md#size)
+
+
+
+
+
+## .writeBlob()
+
+
+**.writeBlob**( *blob* : 4D.Blob )
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|*blob*|[4D.Blob](BlobClass)|→|Blob to write in the file|
+
+
+
+#### Description
+
+The `.writeBlob()` function writes *blob* into the file, starting from the current position .
+
+When this function is executed, the current position ([.offset](#offset)) is updated after the last byte written.
+
+#### See also
+
+[.readBlob()](#readblob)
+
+
+
+
+
+
+## .writeLine()
+
+
+**.writeLine**( *lineOfText* : string )
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|*lineOfText*|string|→|string to write|
+
+
+#### Description
+
+The `.writeLine()` function writes *lineOfText* content at the current position and inserts an end-of-line delimiter (unlike the [.writeText()](#writetext) function). By default, a native end-of-line delimiter is used, but you can define another delimiter when [opening the file handle](FileClass.md#open) by setting the [`.breakModeWrite`](#breakmodewrite) property.
+
+When this function is executed, the current position ([.offset](#offset)) is updated after the end-of-line delimiter.
+
+#### See also
+
+[.breakModeWrite](#breakmodewrite), [.readLine()](#readline), [.writeText()](#writetext)
+
+
+
+
+
+## .writeText()
+
+
+**.writeText**( *textToWrite* : string )
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|*textToWrite*|string|→|string to write|
+
+
+#### Description
+
+The `.writeText()` function writes *textToWrite* content at the current position and does not insert a final end-of-line delimiter (unlike the [.writeLine()](#writeline) function). This function replaces all original end-of-line delimiters. By default, the native delimiter is used, but you can define another delimiter when [opening the file handle](FileClass.md#open) by setting the [`.breakModeWrite`](#breakmodewrite) property.
+
+When this function is executed, the current position ([.offset](#offset)) is updated after the next end-of-line delimiter.
+
+#### See also
+
+[.breakModeWrite](#breakmodewrite), [.readText()](#readtext), [.writeLine()](#writeline)
+
+
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/FolderClass.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/FolderClass.md
new file mode 100644
index 000000000..b8de0e886
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/FolderClass.md
@@ -0,0 +1,292 @@
+---
+id: FolderClass
+title: Folder
+---
+
+
+
+`Folder` objects are created with the [`folder`](commands/folder) command. They contain references to folders that may or may not actually exist on disk. For example, when you execute the `folder` command to create a new folder, a valid `folder` object is created but nothing is actually stored on disk until you call the [`folder.create()`](#create) function.
+
+### Example
+
+The following example creates a "JohnSmith" folder object:
+
+```qs
+var curfolder : 4D.Folder
+curfolder = folder("/PACKAGE/JohnSmith")
+```
+
+### Pathnames
+
+`folder` objects support several pathnames, including `filesystems` or `posix` syntax. Supported pathnames are detailed in the [**Pathnames**](basics/lang-pathnames.md) page.
+
+
+### Functions and properties
+
+||
+|---|
+|[](#copyto) |
+|[](#create) |
+|[](#createalias) |
+|[](#delete) |
+|[](#exists) |
+|[](#extension) |
+|[](./commands/file) |
+|[](./commands/file) |
+|[](commands/folder) |
+|[](#folders) |
+|[](#fullname) |
+|[](#hidden) |
+|[](#isalias) |
+|[](#isfile) |
+|[](#isfolder) |
+|[](#ispackage) |
+|[](#modificationdate) |
+|[](#modificationtime) |
+|[](#name) |
+|[](#original) |
+|[](#parent) |
+|[](#path) |
+|[](#moveto) |
+|[](#rename) |
+
+
+
+## 4D.Folder.new()
+
+
+
+**4D.Folder.new** ( *path* : string ) : 4D.Folder
+
+
+#### Description
+
+The `4D.Folder.new()` function creates and returns a new object of the `4D.Folder` type. It is identical to the [`folder`](commands/folder.md) command (shortcut).
+
+> It is recommended to use the [`folder`](commands/folder.md) shortcut command instead of `4D.Folder.new()`.
+
+
+
+
+## .create()
+
+**.create()** : boolean
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|Result|boolean|←|true if the folder was created successfully, false otherwise|
+
+
+
+#### Description
+
+The `.create()` function creates a folder on disk according to the properties of the `folder` object.
+
+If necessary, the function creates the folder hierachy as described in the [path](#path) property. If the folder already exists on disk, the function does nothing (no error is thrown) and returns false.
+
+**Returned value**
+
+* **true** if the folder is created successfully,
+* **false** if a folder with the same name already exists or if an error occured.
+
+#### Example 1
+
+Create an empty folder in the database folder:
+
+```qs
+var created : boolean
+created = folder("/PACKAGE/SpecialPrefs").create()
+```
+
+#### Example 2
+
+Creation of the "/Archives2019/January/" folder in the database folder:
+
+```qs
+var newFolder : 4D.Folder
+var info : string
+newFolder = folder("/PACKAGE/Archives2019/January")
+if(newFolder.create())
+ info = "The "+newFolder.name+" folder was created."
+else
+ info = "Impossible to create a "+newFolder.name+" folder."
+end
+```
+
+
+
+
+## .createAlias()
+
+**.createAlias**( *destinationFolder* : 4D.Folder , *aliasName* : string ) : 4D.File
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|destinationFolder|4D.Folder|→|Destination folder for the alias or shortcut|
+|aliasName|string|→|Name of the symbolic link|
+|Result|4D.File|←|Alias or shortcut reference|
+
+#### Description
+
+The `.createAlias()` function creates a symbolic link to the folder with the specified *aliasName* name in the folder designated by the *destinationFolder* object.
+
+Pass the name of the symbolic link to create in the *aliasName* parameter.
+
+**Returned object**
+
+A `4D.File` object with the `isAlias` property set to **true**.
+
+#### Example
+
+You want to create a symbolic link to an archive folder in your database folder:
+
+```qs
+var myFolder : 4D.Folder
+myFolder = folder("/PACKAGE/Documents/Archives/2019/January")
+aliasFile = myFolder.createAlias(folder("/PACKAGE"),"Jan2019")
+```
+
+
+
+## .delete()
+
+**.delete**( \{ *option* : integer \} )
+
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|option |integer|→|folder deletion option|
+
+#### Description
+
+The `.delete()` function deletes the folder.
+
+By default, for security reasons, if you omit the *option* parameter, `.delete( )` only allows empty folders to be deleted. If you want the command to be able to delete folders that are not empty, you must use the *option* parameter with one of the following constants:
+
+|Constant| Value| Comment|
+|---|---|---|
+|`kDeleteOnlyIfEmpty`| 0| Deletes folder only when it is empty|
+|`kDeleteWithContents`| 1| Deletes folder along with everything it contains|
+
+When `kDeleteOnlyIfEmpty` is passed or if you omit the *option* parameter:
+
+* The folder is only deleted if it is empty, otherwise, the command does nothing and an error -47 is generated.
+* If the folder does not exist, the error -120 is generated.
+
+When `kDeleteWithContents` is passed:
+
+* The folder, along with all of its contents, is deleted.
+**Warning**: Even when this folder and/or its contents are locked or set to read-only, if the current user has suitable access rights, the folder (and contents) is still deleted.
+* If this folder, or any of the files it contains, cannot be deleted, deletion is aborted as soon as the first inaccessible element is detected, and error -45 (The file is locked or the pathname is not correct) is returned. In this case, the folder may be only partially deleted. When deletion is aborted, you can use the [`lastErrors`](commands/lastErrors.md) command to retrieve the name and path of the offending file.
+* If the folder does not exist, the command does nothing and no error is returned.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## .moveTo()
+
+
+**.moveTo**( *destinationFolder* : 4D.Folder \{ , *newName* : string \} ) : 4D.Folder
+
+
+
+|Parameter|Type||Description|
+|---|----|---|---|
+|destinationFolder|4D.Folder|→|Destination folder|
+|newName|string|→|Full name for the moved folder|
+|Result|4D.Folder|←|Moved folder|
+
+#### Description
+
+The `.moveTo()` function moves or renames the `folder` object (source folder) into the specified *destinationFolder*.
+
+The *destinationFolder* must exist on disk, otherwise an error is generated.
+
+By default, the folder retains its name when moved. If you want to rename the moved folder, pass the new full name in the *newName* parameter. The new name must comply with naming rules (e.g., it must not contain characters such as ":", "/", etc.), otherwise an error is returned.
+
+**Returned object**
+
+The moved `folder` object.
+
+#### Example
+
+You want to move and rename a folder:
+
+```qs
+ var tomove, tomove2 : 4D.Folder
+ tomove = folder("/SOURCES/Shared/Pictures")
+ tomove2 = tomove.moveTo(folder("/SOURCES/Shared/Archives"),"Pic_Archives")
+```
+
+
+
+
+
+
+
+
+
+
+
+
+## .rename()
+
+**.rename**( *newName* : string ) : 4D.Folder
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|newName|string|→|New full name for the folder|
+|Result|4D.Folder|←|Renamed folder|
+
+#### Description
+
+The `.rename()` function renames the folder with the name you passed in *newName* and returns the renamed `folder` object.
+
+The *newName* parameter must comply with naming rules (e.g., it must not contain characters such as ":", "/", etc.), otherwise an error is returned. If a file with the same name already exists, an error is returned.
+
+**Returned object**
+
+The renamed `folder` object.
+
+#### Example
+
+```qs
+ var toRename : 4D.Folder
+ toRename = folder("/SOURCES/Shared/Pictures").rename("Images")
+```
+
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/FunctionClass.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/FunctionClass.md
new file mode 100644
index 000000000..292d5945d
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/FunctionClass.md
@@ -0,0 +1,238 @@
+---
+id: FunctionClass
+title: Function
+---
+
+
+A **`4D.Function`** object contains a piece of code that can be executed from an object, either using the `()` operator, or using the [`apply()`](#apply) and [`call()`](#call) functions. QodlyScript proposes three kinds of `Function` objects:
+
+- **native functions**, i.e. built-in functions from various 4D classes such as `collection.sort()` or `file.copyTo()`.
+- **user functions**, created in user [classes](basics/lang-classes.md) using the [Function keyword](basics/lang-classes.md#function).
+- **formula functions**, i.e. functions that can execute any formula.
+
+
+
+### formula objects
+
+The [formula](commands/formula.md) and [formulaFromString](commands/formulaFromString.md) commands allow you to create **formula functions,** i.e. `4D.Function` objects to execute any expression or code expressed as text.
+
+formula objects can be encapsulated in object properties:
+
+```qs
+ var f : 4D.Function
+ f = newObject
+ f.comp = formula(1+2)
+```
+
+This property is an "object function", i.e. a function which is bound to its parent object. To execute a function stored in an object property, use the **()** operator after the property name, such as:
+
+```qs
+ f.comp() //returns 3
+```
+
+Syntax with brackets is also supported:
+
+```qs
+ f["comp"]() //returns 3
+```
+
+Note that, even if it does not have parameters (see below), an object function to be executed must be called with () parenthesis. Calling only the object property will return a new reference to the formula (and will not execute it):
+
+```qs
+ o = f.comp //returns the formula object in o
+```
+
+You can also execute a function using the [`apply()`](#apply) and [`call()`](#call) functions:
+
+```qs
+ f.comp.apply() //returns 3
+```
+
+#### Passing parameters
+
+You can pass parameters to your formulas using sequentially numbered "$" variables: **$1**, **$2**, **$3**, and so on. The numbering of the variables represents the order of the parameters.
+
+ For example, you can write:
+
+```qs
+
+ var f : object
+ f = newObject
+ f.comp = formula(1+$1)
+ f.comp(5) //returns 6
+```
+
+Or using the [.call()](#call) function:
+
+```qs
+ var f : 4D.Function
+ var r : integer
+ f = formula($1+$2)
+ r = f.call(null,5,5) //r: 10
+ r = f.call(null,10,yearOf(currentDate)) //r: 2033
+```
+
+#### Parameters to a single method
+
+For more convenience, when the formula is made of a single method, parameters can be omitted in the formula object initialization. They can just be passed when the formula is called. For example:
+
+```qs
+ var f : 4D.Function
+ var t : string
+
+ f = formula(myMethod)
+ //Writing formula(myMethod($1,$2)) is not necessary
+ t = f.call(null,"Hello","World") //returns "Hello World"
+ t = f.call() //returns "How are you?"
+
+ //myMethod
+ declare (param1 : string, param2 : string)->return : string
+ if(countParameters = 2)
+ return = param1+" "+param2
+ else
+ return = "How are you?"
+ end
+```
+
+Parameters are received within the method, in the order they are specified in the call.
+
+
+### Functions and properties
+
+
+||
+|---|
+|[](#apply) |
+|[](#call) |
+|[](#source) |
+
+
+
+
+
+## .apply()
+
+**.apply**() : any **.apply**( *thisObj* : object \{ , *formulaParams* : collection \} ) : any
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|thisObj|object|→|Object to be returned by `this` in the formula|
+|formulaParams |collection|→|Collection of values to be passed as $1...$n when `formula` is executed|
+|Result|any|←|Value from formula execution|
+
+
+#### Description
+
+The `.apply()` function executes the `formula` object to which it is applied and returns the resulting value. The formula object can be created using the `formula` or `formulaFromString` commands.
+
+
+In the *thisObj* parameter, you can pass a reference to the object to be used as `this` within the formula.
+
+You can also pass a collection to be used as $1...$n parameters in the formula using the optional *formulaParams* parameter.
+
+Note that `.apply()` is similar to [`.call()`](#call) except that parameters are passed as a collection. This can be useful for passing calculated results.
+
+
+#### Example 1
+
+```qs
+ var f : 4D.Function
+ f = formula($1+$2+$3)
+
+ c = newCollection(10,20,30)
+ result = f.apply(null,c) // returns 60
+```
+
+
+#### Example 2
+
+```qs
+ var calc : 4D.Function
+ var feta, robot : object
+ robot = newObject("name","Robot","price",543,"quantity",2)
+ feta = newObject("name","Feta","price",12.5,"quantity",5)
+
+ calc = formula(this.total = this.price*this.quantity)
+
+ calc.apply(feta) // feta = {name:Feta,price:12.5,quantity:5,total:62.5}
+ calc.apply(robot) // robot = {name:Robot,price:543,quantity:2,total:1086}
+```
+
+
+
+
+
+## .call()
+
+
+**.call**() : any **.call**( *thisObj* : object \{ , ...*params* : any \} ) : any
+
+
+
+|Parameter|Type||Description|
+|---|---|---|---|
+|thisObj|object|→|object to be returned by `this` in the formula|
+|params |any|→|Value(s) to be passed as $1...$n when formula is executed|
+|Result|any|←|Value from formula execution|
+
+
+#### Description
+
+The `.call()` function executes the `formula` object to which it is applied and returns the resulting value. The formula object can be created using the `formula` or `formulaFromString` commands.
+
+In the *thisObj* parameter, you can pass a reference to the object to be used as `this` within the formula.
+
+You can also pass values to be used as *$1...$n* parameters in the formula using the optional *params* parameter(s).
+
+Note that `.call()` is similar to [`.apply()`](#apply) except that parameters are passed directly.
+
+#### Example 1
+
+```qs
+ var f : 4D.Function
+ var result : string
+ f = formula(uppercase($1))
+ result = f.call(null,"hello") // returns "HELLO"
+```
+
+#### Example 2
+
+```qs
+ var f : 4D.Function
+ var o : object
+ var result : integer
+ o = newObject("value",50)
+ f = formula(this.value*2)
+ result = f.call(o) // returns 100
+```
+
+
+
+
+
+
+## .source
+
+**.source** : string
+
+
+#### Description
+
+The `.source` property contains the source expression of the `formula` as text.
+
+This property is **read-only**.
+
+#### Example
+
+```qs
+ var of : 4D.Function
+ var tf : string
+ of = formula(string(currentTime,HH MM AM PM))
+ tf = of.source //"string(currentTime,HH MM AM PM)"
+```
+
+
+
+
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/HTTPRequestClass.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/HTTPRequestClass.md
new file mode 100644
index 000000000..6dd6b6d77
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/HTTPRequestClass.md
@@ -0,0 +1,399 @@
+---
+id: HTTPRequestClass
+title: HTTPRequest
+---
+
+The `HTTPRequest` class allows you to handle [`HTTPRequest objects`](#functions-and-properties) that can be used to configure and send requests to an HTTP server, as well as to process the HTTP server responses.
+
+The `HTTPRequest` class is available from the `4D` class store. You create and send HTTP requests using the [4D.HTTPRequest.new()](#4dhttprequestnew) function, that returns a [`HTTPRequest object`](#functions-and-properties).
+
+
+### Example
+
+Create a `MyHttpRequestOptions` class for the request options:
+
+```qs
+constructor(method : string, headers : object, body : string)
+ this.method = method
+ this.headers = headers
+ this.body = body
+
+Function onResponse(request : 4D.HTTPRequest, event : object)
+//My onResponse method, if you want to handle the request asynchronously
+
+Function onError(request : 4D.HTTPRequest, event : object)
+//My onError method, if you want to handle the request asynchronously
+```
+
+You can now create your request:
+
+```qs
+var headers : object
+headers = newObject()
+headers["field1"] = "value1"
+
+var myHttpRequestOptions : cs.MyHttpRequestOptions
+myHttpRequestOptions = cs.MyHttpRequestOptions.new("GET", headers, "")
+
+var request : 4D.HTTPRequest
+request = 4D.HTTPRequest.new("www.google.com", myHttpRequestOptions)
+request.wait() //If you want to handle the request synchronously
+//Now you can use request.response to access the result of the request or request.error to check the error that happened.
+```
+
+### Functions and properties
+
+An HTTPRequest object is a non-sharable object.
+
+HTTPRequest objects provide the following functions and properties:
+
+||
+|---|
+|[](#4dhttprequestnew) |
+|[](#datatype) |
+|[](#encoding) |
+|[](#errors) |
+|[](#headers) |
+|[](#method) |
+|[](#protocol) |
+|[](#response) |
+|[](#returnresponsebody) |
+|[](#terminate) |
+|[](#terminated) |
+|[](#timeout) |
+|[](#url) |
+|[](#wait) |
+
+
+## 4D.HTTPRequest.new()
+
+
+**4D.HTTPRequest.new**( *url* : string \{ , *options* : object \} ) : 4D.HTTPRequest
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|url|string|→|URL to which to send the request|
+|options|object|→|Request configuration properties|
+|Result|4D.HTTPRequest|←|New HTTPRequest object|
+
+#### Description
+
+The `4D.HTTPRequest.new()` function creates and sends a HTTP request to the HTTP server defined in *url* with the defined *options*, and returns a `4D.HTTPRequest` object.
+
+The returned `4D.HTTPRequest` object is used to process responses from the HTTP server and call functions.
+
+In *url*, pass the URL where you want to send the request. The syntax to use is:
+
+```
+{http://}[{user}:[{password}]@]host[:{port}][/{path}][?{queryString}]
+{https://}[{user}:[{password}]@]host[:{port}][/{path}][?{queryString}]
+```
+
+If you omit the protocol part (`http://` or `https://`), a https request is sent.
+
+For example, you can pass the following strings:
+
+```
+ http://www.myserver.com
+ www.myserver.com/path
+ http://www.myserver.com/path?name = "jones"
+ https://www.myserver.com/login
+ http://123.45.67.89:8083
+ http://john:smith@123.45.67.89:8083
+ http://[2001:0db8:0000:0000:0000:ff00:0042:8329]
+ http://[2001:0db8:0000:0000:0000:ff00:0042:8329]:8080/index.html
+```
+
+#### `options` parameter
+
+In the *options* parameter, pass an object that can contain the following properties:
+
+|Property|Type|Description|Default|
+|---|---|---|---|
+|body|variant|Body of the request (required in case of `post` or `put` requests). Can be a text, a blob, or an object. The content-type is determined from the type of this property unless it is set inside the headers|undefined|
+|certificatesFolder|[folder](FolderClass.md)|Sets the active client certificates folder|undefined|
+|dataType|string|Type of the response body attribute. Values: "text", "blob", "object", or "auto". If "auto", the type of the body content will be deduced from its MIME type (object for JSON, text for text, javascript, xml, http message and url encoded form, blob otherwise)|"auto"|
+|decodeData|boolean|If true, the data received in the `onData` callback is uncompressed|false|
+|encoding|string|Used only in case of requests with a `body` (`post` or `put` methods). Encoding of the request body content if it's a text, ignored if content-type is set inside the headers|"UTF-8"|
+|headers|object|Headers of the request. Syntax: `headers.key = value` (*value* can be a collection if the same key must appear multiple times)|Empty object|
+|method|string|"POST", "GET", or other method|"GET"|
+|minTLSVersion|string|Sets the minimum version of TLS: "`TLSv1_0`", "`TLSv1_1`", "`TLSv1_2`", "`TLSv1_3`"|"`TLSv1_2`"|
+|onData|[function](FunctionClass.md)|Callback when data from the body is received. It receives two objects as parameters (see below)|undefined|
+|onError|[function](FunctionClass.md)|Callback when an error occurs. It receives two objects as parameters (see below)|undefined|
+|onHeaders|[function](FunctionClass.md)|Callback when the headers are received. It receives two objects as parameters (see below)|undefined|
+|onResponse|[function](FunctionClass.md)|Callback when a response is received. It receives two objects as parameters (see below)|undefined|
+|onTerminate|[function](FunctionClass.md)|Callback when the request is over. It receives two objects as parameters (see below)|undefined|
+|protocol|string|"auto" or "HTTP1". "auto" means HTTP1 in the current implementation|"auto"|
+|proxyAuthentication|[authentication object](#authentication-object)|object handling proxy authentication|undefined|
+|serverAuthentication|[authentication object](#authentication-object)|object handling server authentication|undefined|
+|returnResponseBody|boolean|If false, the response body is not returned in the [`response` object](#response). Returns an error if false and `onData` is undefined|true|
+|timeout|number|Timeout in seconds. Undefined = no timeout|undefined|
+
+
+#### Callback functions
+
+All callback functions receive two object parameters:
+
+|Parameter|Type|
+|---|---|
+|param1|[`HTTPRequest` object](#functions-and-properties)|
+|param2|[`Event` object](#event-object)|
+
+Here is the sequence of callback calls:
+
+1. `onHeaders` is always called once
+2. `onData` is called zero or several times (not called if the request does not have a body)
+3. If no error occured, `onResponse` is always called once
+4. If an error occurs, `onError` is executed once (and terminates the request)
+5. `onTerminate` is always executed once
+
+#### event object
+
+An `event` object is returned when a [callback function](#callback-functions) is called. It contains the following properties:
+
+|Property|Type|Description|
+|---|---|---|
+|.data|blob|Received data. It is always *undefined* except in the `onData` callback|
+|.type|text|Type of event. Possible values: "response", "error", "headers", "data", or "terminate|
+
+#### authentication object
+
+An authentication object handles the `options.serverAuthentication` or `options.proxyAuthentication` property. It can contain the following properties:
+
+|Property|Type|Description|Default|
+|---|---|---|---|
+|name|string|Name used for authentication|undefined|
+|password|string|Password used for authentication|undefined|
+|method|string|Method used for authentication: "basic", "digest", "auto"|"auto"|
+
+
+
+
+## .dataType
+
+**dataType** : string
+
+#### Description
+
+The `.dataType` property contains the `dataType` passed in the [`options`](#options-parameter) object when calling [new()](#4dhttprequestnew), "auto" if it was omitted.
+
+
+
+
+## .encoding
+
+**encoding** : string
+
+#### Description
+
+The `.encoding` property contains the `encoding` passed in the [`options`](#options-parameter) object when calling [new()](#4dhttprequestnew), "UTF-8" if it was omitted.
+
+
+
+
+## .errors
+
+**errors** : collection
+
+#### Description
+
+The `.errors` property contains the collection of all the errors if at least one error has been triggered.
+
+Here is the contents of the `.errors` property:
+
+|Property||Type|Description|
+|---|---|---|---|
+|errors||collection|Qodly error stack in case of error|
+||[].errCode|Number|Qodly error code|
+||[].message|string|Description of the Qodly error|
+||[].componentSignature|string|Signature of the internal component which returned the error|
+
+
+
+
+## .headers
+
+**headers** : object
+
+#### Description
+
+The `.headers` property contains the `headers` passed in the [`options`](#options-parameter) object when calling [new()](#4dhttprequestnew). If it was omitted, contains an empty object.
+
+
+
+
+
+## httpParseMessage
+
+
+**httpParseMessage**( *data* : string ) : object **httpParseMessage**( *data* : blob ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|data|string, blob|→|Data to be parsed|
+|Result|Object|←|Object, each property is a part of the multipart data|
+
+#### Description
+
+The `httpParseMessage` command parses a multipart/form-data text or blob (HTTP "response" message) and extracts the content to an object. Each property of the returned object corresponds to a part of the multipart data.
+
+:::info
+
+HTTP itself is a stateless communication protocol. Within this framework, clients initiate communication by sending "request" messages to servers, specifying details like method, target, headers, content, etc. Servers, in turn, respond with "response" messages that include the same details. `HTTP Parse message` parses either the "request" or the "response" message into a well-organized object.
+
+:::
+
+
+#### Example
+
+```qs
+var request : string=file("/SOURCES/Shared/HTTPrequest.txt").getText()
+var parsedMessage : object=httpParseMessage(request)
+//parsedMessage= {
+//headers:{"User-Agent":"XX/20.4.0",...},
+//parts:[{"contentType":"application/http","contentID":"item1",...}],
+//requestLine:"POST /batch/gmail/v1/ HTTP/1.1"
+//}
+```
+
+
+
+
+
+
+## .method
+
+**method** : string
+
+#### Description
+
+The `.method` property contains the `method` passed in the [`options`](#options-parameter) object when calling [new()](#4dhttprequestnew). If it was omitted, contains "GET".
+
+
+
+
+
+## .protocol
+
+**protocol** : string
+
+#### Description
+
+The `.protocol` property contains the `protocol` passed in the [`options`](#options-parameter) object when calling [new()](#4dhttprequestnew). If it was omitted or if "auto" was used, contains the version of the protocol used.
+
+
+
+
+
+## .response
+
+**response** : object
+
+#### Description
+
+The `.response` property contains the response to the request if it has received at least the status code, undefined otherwise.
+
+A `response` object is a non-sharable object. It provides the following properties:
+
+|Property|Type|Description|
+|---|---|---|
+|.body|variant|Body of the response. The type of the message is defined according to the [`dataType`](#datatype) property. Undefined if the body has not been received yet|
+|.headers|object|Headers of the response. Header names are returned in lowercase. `.key` = value (value can be a collection if the same key appears multiple times). Undefined if the headers have not been received yet. |
+|.status|number|Status code of the response|
+|.statusText|string|Message explaining the status code|
+|.rawHeaders|object|Headers of the response. Header names are returned untouched (with their original case). `.key` = value (value can be a collection if the same key appears multiple times). Undefined if the headers have not been received yet.|
+
+
+
+
+## .returnResponseBody
+
+**returnResponseBody** : boolean
+
+#### Description
+
+The `.returnResponseBody` property contains the `returnResponseBody` passed in the [`options`](#options-parameter) object when calling [new()](#4dhttprequestnew). If it was omitted, contains true.
+
+
+
+
+## .terminate()
+
+**.terminate**()
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+||||Does not require any parameters|
+
+#### Description
+
+The `.terminate()` function aborts the HTTP request. It triggers the `onTerminate` event.
+
+
+
+
+## .terminated
+
+**terminated** : boolean
+
+#### Description
+
+The `.terminated` property contains true if the request is terminated (after the call to `onTerminate`), false otherwise.
+
+
+
+
+## .timeout
+
+**timeout** : number
+
+#### Description
+
+The `.timeout` property contains the `timeout` passed in the [`options`](#options-parameter) object when calling [new()](#4dhttprequestnew). If it was omitted, contains undefined.
+
+
+
+
+## .url
+
+**url** : string
+
+#### Description
+
+The `.url` property contains the URL of the HTTP request.
+
+
+
+
+## .wait()
+
+**.wait**( \{ *time* : number \} ) : HTTPRequestClass
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|time|number|→|Maximum time in seconds to wait for the response|
+|Result|4D.HTTPRequest|←|HTTPRequest object|
+
+#### Description
+
+The `wait()` function waits for the response from the server.
+
+If a *time* parameter is passed, the function will wait at most the defined number of seconds.
+
+If the response from the server has already arrived, the function returns immediately.
+
+:::note
+
+During the `.wait()` execution, callback functions are executed, whether they originate from other instances of the same class, or instances of any classes supporting the same callback mechanism (`HTTPRequest`, `TCPConnection`, `SystemWorker`). You can exit from a `.wait()` by calling `shutdown()` or [`terminate()`](#terminate) (depending on the API) from a callback.
+
+:::
+
+
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/IMAPTransporterClass.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/IMAPTransporterClass.md
new file mode 100644
index 000000000..5a092deac
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/IMAPTransporterClass.md
@@ -0,0 +1,1671 @@
+---
+id: IMAPTransporterClass
+title: IMAPTransporter
+---
+
+The `IMAPTransporter` class allows you to retrieve messages from an IMAP email server.
+
+
+
+### Functions and properties
+
+4D.IMAPTransporter objects provide the following properties and functions:
+
+||
+|---|
+|[](#4dimaptransporternew) |
+|[](#acceptunsecureconnection) |
+|[](#addflags) |
+|[](#append) |
+|[](#authenticationmode) |
+|[](#checkconnection) |
+|[](#checkconnectiondelay) |
+|[](#connectiontimeout) |
+|[](#copy) |
+|[](#createbox) |
+|[](#delete) |
+|[](#deletebox) |
+|[](#expunge) |
+|[](#getboxinfo) |
+|[](#getboxlist) |
+|[](#getdelimiter) |
+|[](#getmail) |
+|[](#getmails) |
+|[](#getmimeasblob) |
+|[](#host) |
+|[](#logfile) |
+|[](#move) |
+|[](#numtoid) |
+|[](#removeflags) |
+|[](#renamebox) |
+|[](#port) |
+|[](#searchmails) |
+|[](#selectbox) |
+|[](#subscribe) |
+|[](#unsubscribe) |
+|[](#user) |
+
+
+## 4D.IMAPTransporter.new()
+
+**4D.IMAPTransporter.new**( *server* : object ) : 4D.IMAPTransporter
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|server|object|→|Mail server information|
+|Result|4D.IMAPTransporter|←|IMAP transporter object|
+
+#### Description
+
+The `4D.IMAPTransporter.new()` function configures a new IMAP connection according to the *server* parameter and returns a new *transporter* object. The returned transporter object will then usually be used to receive emails.
+
+In the *server* parameter, pass an object containing the following properties:
+
+|*server*|Default value (if omitted)|
+|---|---|
+|[](#acceptunsecureconnection) |False|
+|.**accessTokenOAuth2**: string .**accessTokenOAuth2**: object string string or token object representing OAuth2 authorization credentials. Used only with OAUTH2 `authenticationMode`. If `accessTokenOAuth2` is used but `authenticationMode` is omitted, the OAuth 2 protocol is used (if allowed by the server). Not returned in the `IMAP transporter` object.|none|
+|[](#authenticationmode) - |the most secure authentication mode supported by the server is used|
+|[](#checkconnectiondelay) - |300|
+|[](#connectiontimeout) - |30|
+|[](#host) - |*mandatory*
+|[](#logfile) - |none|
+|.**password** : string User password for authentication on the server. Not returned in the `IMAP transporter` object.|none|
+|[](#port) - |993|
+|[](#user) - |none|
+
+>**Warning**: Make sure the defined timeout is lower than the server timeout, otherwise the client timeout will be useless.
+
+#### Result
+
+The function returns an **IMAP transporter object**. All returned properties are **read-only**.
+
+>The IMAP connection is automatically closed when the transporter object is destroyed.
+
+#### Example
+
+```qs
+var server : object
+var transporter : 4D.IMAPTransporter
+var status : object
+var info : string
+
+server = newObject()
+server.host = "imap.gmail.com" //Mandatory
+server.port = 993
+server.user = "qodly@gmail.com"
+server.password = "XXXXXXXX"
+server.logFile = "LogTest.txt" //log to save in the Logs folder
+
+transporter = 4D.IMAPTransporter.new(server)
+
+status = transporter.checkConnection()
+if(not(status.success))
+ info = "An error occurred: "+status.statusText
+end
+```
+
+
+
+
+
+## .addFlags()
+
+
+**.addFlags**( *msgIDs* : collection , *keywords* : object ) : object **.addFlags**( *msgIDs* : string , *keywords* : object ) : object **.addFlags**( *msgIDs* : integer , *keywords* : object ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|msgIDs|collection|→|Collection of strings: Message unique IDs (string)|
+|msgIDs|string| Unique ID of a message|
+|msgIDs|integer|kIMAPAll: All messages in the selected mailbox|
+|keywords|object|→|Keyword flags to add|
+|Result|object|←|Status of the addFlags operation|
+
+#### Description
+
+The `.addFlags()` function adds flags to the *msgIDs* for the specified `keywords`.
+
+In the *msgIDs* parameter, you can pass either:
+
+* a *collection* containing the unique IDs of specific messages or
+* the unique ID (*string*) of a single message or
+* the following constant (*integer*) for all messages in the selected mailbox:
+
+ |Constant |Value |Comment|
+ |---|---|---|
+ |kIMAPAll |1 |Select all messages in the selected mailbox|
+
+The *keywords* parameter lets you define the flags to add to *msgIDs*. You can use the following standard flags as well as custom flags (custom flags support depends on the server implementation):
+
+|Property|Type|Description|
+|---|---|---|
+|$draft |boolean |true to add the "draft" flag to the message |
+|$seen |boolean |true to add the "seen" flag to the message|
+|$flagged |boolean |true to add the "flagged" flag to the message|
+|$answered |boolean |true to add the "answered" flag to the message|
+
+|$deleted |boolean | true to add the "deleted" flag to the message|
+|`` |boolean | true to add the custom flag to the message|
+
+The custom flags names must respect this rule: the keyword must be a case-insensitive string excluding control chars and space and can not include any of these characters: `( ) { ] % * " \`
+
+>* For a keyword to be taken into account it has to be true.
+>* The interpretation of keyword flags may vary per mail client.
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||boolean|true if the operation is successful, False otherwise
+|statusText || string|Status message returned by the IMAP server, or last error returned in the Qodly error stack |
+|errors ||collection|Qodly error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| Qodly error code|
+| |\[].message|string|Description of the Qodly error |
+| |\[].componentSignature|string|Signature of the internal component which returned the error|
+
+#### Example
+
+```qs
+var transporter : 4D.IMAPTransporter
+var options,boxInfo,status : object
+
+options = newObject()
+options.host = "imap.gmail.com"
+options.port = 993
+options.user = "qodly@gmail.com"
+options.password = "xxxxx"
+
+// Create transporter
+transporter = 4D.IMAPTransporter.new(options)
+
+// Select mailbox
+boxInfo = transporter.selectBox("INBOX")
+
+// Mark all messages from INBOX as read/seen
+flags = newObject
+flags["$seen"] = true
+status = transporter.addFlags(kIMAPAll,flags)
+```
+
+
+
+
+## .append()
+
+
+**.append**( *mailObj* : object , *destinationBox* : string , *options* : object ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|mailObj|object|→|Email object|
+|destinationBox|string|→|Mailbox to receive Email object|
+|options|object|→|object containing charset info |
+|Result|object|←|Status of the append operation|
+
+#### Description
+
+The `.append()` function appends a `mailObj` to the `destinationBox`.
+
+In the `mailObj` parameter, pass an Email object. For a comprehensive description of mail properties, see [Email object](EmailObjectClass.md#properties). The `.append()` function supports keyword tags in the Email object's `keywords` attribute.
+
+The optional `destinationBox` parameter lets you pass the name of a mailbox where the `mailObj` will be appended. If omitted, the current mailbox is used.
+
+In the optional `options` parameter, you can pass an object to define the charset and encoding for specific parts of the email. Available properties:
+
+|Property|Type|Description|
+|---|---|---|
+|headerCharset|string|Charset and encoding used for the following parts of the email: subject, attachment filenames, and email name attribute(s). Possible values: See possible charsets table below|
+|bodyCharset|string|Charset and encoding used for the html and string body contents of the email. Possible values: See possible charsets table below |
+
+Possible charsets:
+
+|Constant|Value|Comment|
+|---|---|---|
+|kMailModeUTF8|US-ASCII_UTF8_QP|headerCharset & bodyCharset: US-ASCII if possible, otherwise UTF-8 & Quoted-printable (**default value**)|
+|kMailModeUTF8InBase64|US-ASCII_UTF8_B64|headerCharset & bodyCharset: US-ASCII if possible, otherwise UTF-8 & base64|
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||boolean|true if the operation is successful, False otherwise
+|statusText || string|Status message returned by the IMAP server, or last error returned in the Qodly error stack |
+|errors ||collection|Qodly error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| Qodly error code|
+| |\[].message|string|Description of the Qodly error |
+| |\[].componentSignature|string|Signature of the internal component which returned the error|
+
+#### Example
+
+To save an email in the Drafts mailbox:
+
+```qs
+var imap : 4D.IMAPTransporter
+var settings, status, msg: object
+
+settings = newObject("host", "domain.com", "user", "xxxx", "password", "xxxx", "port", 993)
+
+imap = 4D.IMAPTransporter.new(settings)
+
+msg = newObject()
+msg.from = "xxxx@domain.com"
+msg.subject = "Lorem Ipsum"
+msg.stringBody = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+msg.keywords = newObject
+msg.keywords["$seen"] = true //flag the message as read
+msg.keywords["$draft"] = true //flag the message as a draft
+
+status = imap.append(msg, "Drafts")
+```
+
+
+
+
+
+
+
+
+
+## .checkConnectionDelay
+
+
+**.checkConnectionDelay** : integer
+
+#### Description
+
+The `.checkConnectionDelay` property contains the maximum time (in seconds) allowed prior to checking the connection to the server. If this time is exceeded between two method calls, the connection to the server will be checked. By default, if the property has not been set in the *server* object, the value is 300.
+
+>**Warning**: Make sure the defined timeout is lower than the server timeout, otherwise the client timeout will be useless.
+
+
+
+
+
+
+## .copy()
+
+
+**.copy**( *msgsIDs* : collection , *destinationBox* : string ) : object **.copy**( *allMsgs* : integer , *destinationBox* : string ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|msgsIDs|collection|→|Collection of message unique IDs (strings)|
+|allMsgs|integer|→|`kIMAPAll`: All messages in the selected mailbox|
+|destinationBox|string|→|Mailbox to receive copied messages|
+|Result|object|←|Status of the copy operation|
+
+#### Description
+
+The `.copy()` function copies the messages defined by *msgsIDs* or *allMsgs* to the *destinationBox* on the IMAP server.
+
+You can pass:
+
+* in the *msgsIDs* parameter, a collection containing the unique IDs of the specific messages to copy, or
+* in the *allMsgs* parameter, the `kIMAPAll` constant (integer) to copy all messages in the selected mailbox.
+
+The *destinationBox* parameter allows you to pass a string value with the name of the mailbox where the copies of messages will be placed.
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||boolean|true if the operation is successful, False otherwise
+|statusText || string|Status message returned by the IMAP server, or last error returned in the Qodly error stack |
+|errors ||collection|Qodly error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| Qodly error code|
+| |\[].message|string|Description of the Qodly error |
+| |\[].componentSignature|string|Signature of the internal component which returned the error|
+
+#### Example 1
+
+To copy a selection of messages:
+
+```qs
+ var server,boxInfo,status : object
+ var mailIds : collection
+ var transporter : 4D.IMAPTransporter
+
+ server = newObject()
+ server.host = "imap.gmail.com" //Mandatory
+ server.port = 993
+ server.user = "qodly@gmail.com"
+ server.password = "XXXXXXXX"
+
+ transporter = 4D.IMAPTransporter.new(server)
+
+ //select mailbox
+ boxInfo = transporter.selectBox("inbox")
+
+ //get collection of message unique IDs
+ mailIds = transporter.searchMails("subject \"New feature:\"")
+
+ // copy found messages to the "documents" mailbox
+ status = transporter.copy(mailIds,"documents")
+```
+
+#### Example 2
+
+To copy all messages in the current mailbox:
+
+```qs
+ var server,boxInfo,status : object
+ var transporter : 4D.IMAPTransporter
+
+ server = newObject()
+ server.host = "imap.gmail.com" //Mandatory
+ server.port = 993
+ server.user = "qodly@gmail.com"
+ server.password = "XXXXXXXX"
+
+ transporter = 4D.IMAPTransporter.new(server)
+
+ //select mailbox
+
+ boxInfo = transporter.selectBox("inbox")
+
+ // copy all messages to the "documents" mailbox
+ status = transporter.copy(kIMAPAll,"documents")
+```
+
+
+
+
+## .createBox()
+
+
+**.createBox**( *name* : string ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|name|string|→|Name of the new mailbox|
+|Result|object|←|Status of the mailbox creation operation|
+
+#### Description
+
+The `.createBox()` function creates a mailbox with the given `name`. If the IMAP server’s hierarchy separator character appears elsewhere in the mailbox name, the IMAP server will create any parent names needed to create the given mailbox.
+
+In other words, an attempt to create "Projects/IMAP/Doc" on a server in which "/" is the hierarchy separator character will create:
+
+* Only the "Doc" mailbox if "Projects" & "IMAP" already exist.
+* "IMAP" & "Doc" mailboxes if only "Projects" already exists.
+* "Projects" & "IMAP" & "Doc" mailboxes, if they do not already exist.
+
+In the `name` parameter, pass the name of the new mailbox.
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||boolean|true if the operation is successful, False otherwise
+|statusText || string|Status message returned by the IMAP server, or last error returned in the Qodly error stack |
+|errors ||collection|Qodly error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| Qodly error code|
+| |\[].message|string|Description of the Qodly error |
+| |\[].componentSignature|string|Signature of the internal component which returned the error|
+
+#### Example
+
+To create a new "Invoices" mailbox:
+
+```qs
+var info : string
+var transporter : 4D.IMAPTransporter
+var options, status : object
+
+options = newObject()
+
+options.host = "imap.gmail.com"
+options.user = "test@gmail.com"
+options.password = "XXXX"
+
+transporter = 4D.IMAPTransporter.new(options)
+
+status = transporter.createBox("Invoices")
+
+if(status.success)
+ info = "Mailbox creation successful!"
+else
+ info = "Error: "+status.statusText
+end
+```
+
+
+
+
+## .delete()
+
+**.delete**( *msgsIDs* : collection ) : object **.delete**( *msgsIDs* : integer ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|msgsIDs|collection|→|collection of message unique IDs (strings)|
+|msgsIDs|integer|→|`kIMAPAll`: All messages in the selected mailbox|
+|Result|object|←|Status of the delete operation|
+
+#### Description
+
+The `.delete()` function sets the "deleted" flag for the messages defined in *msgsIDs*.
+
+In the *msgsIDs* parameter, you can pass:
+
+* a collection containing the unique IDs of the specific messages to delete, or
+* the `kIMAPAll` constant (integer) to delete all messages in the selected mailbox.
+
+Executing this function does not actually remove messages. Messages with the "delete" flag can still be found by the [`.searchMails()`](#searchmails) function. Flagged messages are deleted from the IMAP server with the [`.expunge()`](#expunge) function or by selecting another mailbox or when the IMAP transporter object (created with [4D.IMAPTransporter.new](#4dimaptransporternew)) is destroyed.
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||boolean|true if the operation is successful, False otherwise
+|statusText || string|Status message returned by the IMAP server, or last error returned in the Qodly error stack |
+|errors ||collection|Qodly error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| Qodly error code|
+| |\[].message|string|Description of the Qodly error |
+| |\[].componentSignature|string|Signature of the internal component which returned the error|
+
+#### Example 1
+
+To delete a selection of messages:
+
+```qs
+ var server,boxInfo,status : object
+ var mailIds : collection
+ var transporter : 4D.IMAPTransporter
+
+ server = newObject()
+ server.host = "imap.gmail.com" //Mandatory
+ server.port = 993
+ server.user = "qodly@gmail.com"
+ server.password = "XXXXXXXX"
+
+ transporter = 4D.IMAPTransporter.new(server)
+
+ //select mailbox
+ boxInfo = transporter.selectBox("Inbox")
+
+ //get collection of message unique IDs
+ mailIds = transporter.searchMails("subject \"Reports\"")
+
+ // Delete selected messages
+ status = transporter.delete(mailIds)
+```
+
+#### Example 2
+
+To delete all messages in the current mailbox:
+
+```qs
+ var server,boxInfo,status : object
+ var transporter : 4D.IMAPTransporter
+
+ server = newObject()
+ server.host = "imap.gmail.com" //Mandatory
+ server.port = 993
+ server.user = "qodly@gmail.com"
+ server.password = "XXXXXXXX"
+
+ transporter = 4D.IMAPTransporter.new(server)
+
+ //select mailbox
+ boxInfo = transporter.selectBox("Junk Email")
+
+ // delete all messages in the current mailbox
+ status = transporter.delete(kIMAPAll)
+```
+
+
+
+
+## .deleteBox()
+
+
+**.deleteBox**( *name* : string ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|name|string|→|Name of the mailbox to delete|
+|Result|object|←|Status of the mailbox deletion operation|
+
+
+#### Description
+
+The `.deleteBox()` function permanently removes the mailbox with the given *name* from the IMAP server. Attempting to delete an INBOX or a mailbox that does not exist will generate an error.
+
+In the *name* parameter, pass the name of the mailbox to delete.
+
+>* The function cannot delete a mailbox that has child mailboxes if the parent mailbox has the "\Noselect" attribute.
+>* All messages in the deleted mailbox will also be deleted.
+>* The ability to delete a mailbox depends on the mail server.
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||boolean|true if the operation is successful, False otherwise
+|statusText || string|Status message returned by the IMAP server, or last error returned in the Qodly error stack |
+|errors ||collection|Qodly error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| Qodly error code|
+| |\[].message|string|Description of the Qodly error |
+| |\[].componentSignature|string|Signature of the internal component which returned the error|
+
+#### Example
+
+To delete the "Nova Orion Industries" child mailbox from the "Bills" mailbox hierarchy:
+
+```qs
+var pw, name, info : string
+var options, status : object
+var transporter : 4D.IMAPTransporter
+
+options = newObject()
+
+pw = "XXXXXX" //password
+
+options.host = "imap.gmail.com"
+options.user = "test@gmail.com"
+options.password = pw
+
+transporter = 4D.IMAPTransporter.new(options)
+
+// delete mailbox
+name = "Bills"+transporter.getDelimiter()+"Nova Orion Industries"
+status = transporter.deleteBox(name)
+
+if(status.success)
+ info = "Mailbox deletion successful!"
+else
+ info = "Error: "+status.statusText
+end
+```
+
+
+
+
+## .expunge()
+
+**.expunge**() : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|Result|object|←|Status of the expunge operation |
+
+#### Description
+
+The `.expunge()` function removes all messages with the "deleted" flag from the IMAP mail server. The "deleted" flag can be set with the [`.delete()`](#delete) or [`.addFlags()`](#addflags) functions.
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||boolean|true if the operation is successful, False otherwise
+|statusText || string|Status message returned by the IMAP server, or last error returned in the Qodly error stack |
+|errors ||collection|Qodly error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| Qodly error code|
+| |\[].message|string|Description of the Qodly error |
+| |\[].componentSignature|string|Signature of the internal component which returned the error|
+
+#### Example
+
+```qs
+var transporter : 4D.IMAPTransporter
+var options,boxInfo,status : object
+var ids : collection
+
+options = newObject()
+options.host = "imap.gmail.com"
+options.port = 993
+options.user = "qodly@gmail.com"
+options.password = "xxxxx"
+
+// Create transporter
+transporter = 4D.IMAPTransporter.new(options)
+
+// Select mailbox
+boxInfo = transporter.selectBox("INBOX")
+
+// Find and delete all seen messages in INBOX
+ids = transporter.searchMails("SEEN")
+status = transporter.delete(ids)
+
+// Purge all messages flagged as deleted
+status = transporter.expunge()
+```
+
+
+
+
+
+## .getBoxInfo()
+
+
+**.getBoxInfo**(\{ *name* : string \}) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|name|string|→|Name of the mailbox|
+|Result|object|←|boxInfo object|
+
+#### Description
+
+The `.getBoxInfo()` function returns a `boxInfo` object corresponding to the current maibox, or the mailbox *name*. This function returns the same information as [`.selectBox()`](#selectbox) without changing the current mailbox.
+
+In the optional *name* parameter, pass the name of the mailbox to access. The name represents an unambiguous left-to-right hierarchy with levels separated by a specific delimiter character. The delimiter can be found with the [`.getDelimiter()`](#getdelimiter) function.
+
+If the mailbox *name* is not selectable or does not exist, the function generates an error and returns **null**.
+
+**Returned object**
+
+The `boxInfo` object returned contains the following properties:
+
+|Property| Type| Description|
+|---|---|---|
+|name|string|Name of the mailbox
+|mailCount| number| Number of messages in the mailbox|
+|mailRecent| number| Number of messages with the "recent" flag (indicating new messages)|
+|id| string| Unique id of the mailbox|
+|mailUnseen| number| Number of messages with the "unseen" flag |
+
+
+#### Example
+
+```qs
+ var server : object
+ var transporter : 4D.IMAPTransporter
+ var boxInfo : object
+ var info : string
+ transporter = 4D.IMAPTransporter.new(server)
+
+ boxInfo = transporter.getBoxInfo("INBOX")
+ info = "INBOX contains "+string(boxInfo.mailRecent)+" recent emails."
+```
+
+
+
+
+## .getBoxList()
+
+
+**.getBoxList**( \{ *parameters* : object \} ) : collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|parameters|object|→|Parameter object|
+|Result|collection|←|Collection of mailbox objects|
+
+#### Description
+
+The `.getBoxList()` function returns a collection of mailboxes describing all of the available mailboxes. This function allows you to locally manage the list of messages located on the IMAP mail server.
+
+In the optional *parameters* parameter, pass an object containing values to filter the returned mailboxes. You can pass:
+
+|Property | Type| Description |
+|---|---|---|
+|isSubscribed| boolean |
**true** to return only subscribed mailboxes
**false** to return all available mailboxes
|
+| names | collection | Collection of objects containing a "name" attribute or collection of texts containing the box names |
+| withBoxProperties| boolean | If true (default): adds the `selectable`, `inferior`, and `interesting` attributes to the result object. If false, these attributes are omitted.|
+| withBoxInfo| boolean | Default value is false. If true, adds the `mailCount`, `mailRecent`, and `id` attributes to the result object.|
+
+#### Result
+
+Each object of the returned collection contains the following properties:
+
+
+
+|Property| Type|Description |
+|---|---|---|
+|\[].name|string|Name of the mailbox. Returned if withBoxProperties=true or withBoxInfo=true|
+|\[].selectable |boolean |Indicates whether or not the access rights allow the mailbox to be selected:
true - the mailbox can be selected
false - the mailbox can not be selected
Returned if withBoxProperties=true|
+|\[].inferior |boolean |Indicates whether or not the access rights allow creating a lower hierachy in the mailbox:
true - a lower level can be created
false - a lower level can not be created
Returned if withBoxProperties=true|
+|\[].interesting |boolean |Indicates if the mailbox has been marked "interesting" by the server:
true - The mailbox has been marked "interesting" by the server. For example, it may contain new messages.
false - The mailbox has not been marked "interesting" by the server.
Returned if withBoxProperties=true|
+|\[].mailCount | number | Number of messages in inbox. Returned if withBoxInfo=true |
+|\[].mailRecent | number | Number of messages marked "recent" (indicating new messages). Returned if withBoxInfo=true |
+|\[].mailUnseen | number | Number of messages marked "unseen". Returned if withBoxInfo=true |
+|\[].id | string | Unique mailbox identifier. Returned if withBoxInfo=true |
+
+
+If the account does not contain any mailboxes, an empty collection is returned.
+
+>* If there is no open connection, `.getBoxList()` will open a connection.
+>* If the connection has not been used since the designated connection delay (see `4D.IMAPTransporter.new`), the `.checkConnection( )` function is automatically called.
+
+#### Example
+
+```qs
+ var server : object
+ var transporter : 4D.IMAPTransporter
+ var boxList : collection
+ var info, split : string
+ transporter = 4D.IMAPTransporter.new(server)
+
+ boxList = transporter.getBoxList()
+
+ forEach(box,boxList)
+ if(box.interesting)
+ split = splitString(box.name,transporter.getDelimiter())
+ info = "New emails are available in the box: "+split[split.length-1])
+ end
+ end
+```
+
+
+
+
+## .getDelimiter()
+
+
+**.getDelimiter**() : string
+
+
+
+|Parameter|Type||Description|
+|-----|--- |:---:|------|
+|Result|string|←|Hierarchy delimiter character|
+
+#### Description
+
+The `.getDelimiter()` function returns the character used to delimit levels of hierarchy in the mailbox name.
+
+The delimiter is a character which can be used to:
+
+* create lower level (inferior) mailboxes
+* search higher or lower within the mailbox hierarchy
+
+#### Result
+
+Mailbox name delimiter character.
+
+>* If there is no open connection, `.getDelimiter()` will open a connection.
+>* If the connection has not been used since the [designated connection delay](#checkconnectiondelay), the [`.checkConnection()`](#checkconnection) function is automatically called.
+
+#### Example
+
+See [`getBoxList()` example](#getboxlist).
+
+
+
+
+## .getMail()
+
+
+**.getMail**( *msgNumber*: integer \{ , *options* : object \} ) : object **.getMail**( *msgID*: string \{ , *options* : object \} ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|msgNumber|integer|→|Sequence number of the message|
+|msgID|string|→|Unique ID of the message|
+|options|object|→|Message handling instructions|
+|Result|object|←|[Email object](EmailObjectClass.md#properties)|
+
+#### Description
+
+The `.getMail()` function returns the `Email` object corresponding to the *msgNumber* or *msgID* in the mailbox designated by the `IMAP transporter` object. This function allows you to locally handle the email contents.
+
+In the first parameter, you can pass either:
+
+* *msgNumber*, an *integer* value indicating the sequence number of the message to retrieve or
+* *msgID*, a *string* value indicating the unique ID of the message to retrieve.
+
+The optional *options* parameter allows you pass an object defining additional instructions for handling the message. The following properties are available:
+
+|Property | Type | Description |
+|---|---|---|
+|updateSeen|boolean|If true, the message is marked as "seen" in the mailbox. If False, the message is not marked as "seen". Default value: true|
+|withBody|boolean | Pass true to return the body of the message. If False, only the message header is returned. Default value: true|
+
+>* The function generates an error and returns **Null** if *msgID* designates a non-existing message,
+>* If no mailbox is selected with the [`.selectBox()`](#selectbox) function, an error is generated,
+>* If there is no open connection, `.getMail()` will open a connection the last mailbox specified with [`.selectBox()`](#selectbox)`.
+
+#### Result
+
+`.getMail()` returns an [`Email` object](EmailObjectClass) with the following specific IMAP properties: *id*, *receivedAt*, and *size*.
+
+#### Example
+
+You want to get the message with ID = 1:
+
+```qs
+ var server : object
+ var info, mail, boxInfo : variant
+ var transporter : 4D.IMAPTransporter
+
+ server = newObject()
+ server.host = "imap.gmail.com" //Mandatory
+ server.port = 993
+ server.user = "qodly@gmail.com"
+ server.password = "XXXXXXXX"
+
+ //create transporter
+ transporter = 4D.IMAPTransporter.new(server)
+
+ //select mailbox
+ boxInfo = transporter.selectBox("Inbox")
+
+ //get Email object with ID 1
+ mail = transporter.getMail(1)
+```
+
+
+
+
+## .getMails()
+
+
+**.getMails**( *ids* : collection \{ , *options* : object \} ) : object **.getMails**( *startMsg* : integer , *endMsg* : integer \{ , *options* : object \} ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|ids |collection|→|Collection of message ID|
+|startMsg|integer|→|Sequence number of the first message|
+|endMsg |integer|→|Sequence number of the last message|
+|options|object|→|Message handling instructions|
+|Result|object|←|Object containing:
a collection of [Email objects](EmailObjectClass.md#properties) and
a collection of IDs or numbers for missing messages, if any
|
+
+#### Description
+
+The `.getMails()` function returns an object containing a collection of `Email` objects.
+
+**First Syntax:**
+
+***.getMails( ids \{ , options \} ) -> result***
+
+The first syntax allows you to retrieve messages based on their IDs.
+
+In the *ids* parameter, pass a collection of IDs for the messages to return. You can get the IDs with [`.getMail()`](#getmail).
+
+The optional *options* parameter allows you to define the parts of the messages to be returned. See the **Options** table below for a description of the available properties.
+
+**Second syntax:**
+
+ ***.getMails( startMsg , endMsg \{ , options \} ) -> result***
+
+The second syntax allows you to retrieve messages based on a sequential range. The values passed represent the position of the messages in the mailbox.
+
+In the *startMsg* parameter, pass an *integer* value corresponding to the number of the first message in a sequential range. If you pass a negative number (*startMsg* <= 0), the first message of the mailbox will be used as the beginning of the sequence.
+
+In the *endMsg* parameter, pass an *integer* value corresponding to the number of the last message to be included in a sequential range. If you pass a negative number (*endMsg* <= 0), the last message of the mailbox will be used as the end of the sequence.
+
+The optional *options* parameter allows you to define the parts of the messages to be returned.
+
+**Options**
+
+|Property | Type| Description |
+|---|---|---|
+|updateSeen | boolean | If true, the specified messages are marked as "seen" in the mailbox. If False, the messages are not marked as "seen". Default value: true |
+|withBody | boolean | Pass true to return the body of the specified messages. If false, only the message headers are returned. Default value: true|
+
+>* If no mailbox is selected with the [`.selectBox()`](#selectbox) command, an error is generated.
+>* If there is no open connection, `.getMails()` will open a connection the last mailbox specified with [`.selectBox()`](#selectbox).
+
+#### Result
+
+`.getMails()` returns an object containing the following collections:
+
+|Property | Type | Description |
+|---|---|---|
+|list |collection |collection of [`Email` objects](EmailObjectClass.md#properties). If no Email objects are found, an empty collection is returned.|
+|notFound |collection| collection of:
first syntax - previously passed message IDs that do not exist
second syntax - sequence numbers of messages between startMsg and endMsg that do not exist
An empty collection is returned if all messages are found.|
+
+#### Example
+
+You want to retrieve the 20 most recent emails without changing their "seen" status:
+
+```qs
+ var server,boxInfo,result : object
+ var transporter : 4D.IMAPTransporter
+
+ server = newObject()
+ server.host = "imap.gmail.com" //Mandatory
+ server.port = 993
+ server.user = "qodly@gmail.com"
+ server.password = "XXXXXXXX"
+
+ //create transporter
+ transporter = 4D.IMAPTransporter.new(server)
+
+ //select mailbox
+ boxInfo = transporter.selectBox("INBOX")
+
+ if(boxInfo.mailCount>0)
+ // retrieve the headers of the last 20 messages
+ // without marking them as read
+ result = transporter.getMails(boxInfo.mailCount-20,boxInfo.mailCount,\
+ newObject("withBody",false,"updateSeen",false))
+ forEach(mail,result.list)
+ // ...
+ end
+ end
+```
+
+
+
+
+## .getMIMEAsblob()
+
+**.getMIMEAsblob**( *msgNumber* : integer \{ , *updateSeen* : boolean \} ) : blob **.getMIMEAsblob**( *msgID* : string \{ , *updateSeen* : boolean \} ) : blob
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|msgNumber|integer|→ |Sequence number of the message|
+|msgID|string|→ |Unique ID of the message|
+|updateSeen|boolean|→|If true, the message is marked "seen" in the mailbox. If False the message is left untouched.|
+|Result|BLOB|←|blob of the MIME string returned from the mail server|
+
+#### Description
+
+The `.getMIMEAsblob()` function returns a BLOB containing the MIME contents for the message corresponding to the *msgNumber* or *msgID* in the mailbox designated by the `IMAP_transporter`.
+
+In the first parameter, you can pass either:
+
+* *msgNumber*, an *integer* value indicating the sequence number of the message to retrieve or
+* *msgID*, a *string* value indicating the unique ID of the message to retrieve.
+
+The optional *updateSeen* parameter allows you to specify if the message is marked as "seen" in the mailbox. You can pass:
+
+* **true** - to mark the message as "seen" (indicating the message has been read)
+* **false** - to leave the message's "seen" status untouched
+
+>* The function returns an empty blob if *msgNumber* or msgID* designates a non-existing message,
+>* If no mailbox is selected with the [`.selectBox()`](#selectbox) command, an error is generated,
+>* If there is no open connection, `.getMIMEAsblob()` will open a connection the last mailbox specified with `.selectBox()`.
+
+#### Result
+
+`.getMIMEAsblob()` returns a `blob` which can be archived in a database or converted to an [`Email` object](EmailObjectClass.md#properties) with the `mailConvertFromMIME` command.
+
+#### Example
+
+```qs
+ var server : object
+ var boxInfo : variant
+ var blob : blob
+ var transporter : 4D.IMAPTransporter
+
+ server = newObject()
+ server.host = "imap.gmail.com"
+ server.port = 993
+ server.user = "qodly@gmail.com"
+ server.password = "XXXXXXXX"
+
+ //create transporter
+ transporter = 4D.IMAPTransporter.new(server)
+
+ //select mailbox
+ boxInfo = transporter.selectBox("Inbox")
+
+ //get BLOB
+ blob = transporter.getMIMEAsblob(1)
+```
+
+
+
+
+
+
+
+
+## .move()
+
+
+**.move**( *msgsIDs* : collection , *destinationBox* : string ) : object **.move**( *allMsgs* : integer , *destinationBox* : string ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|msgsIDs|collection|→|collection of message unique IDs (strings)|
+|allMsgs|integer|→|`kIMAPAll`: All messages in the selected mailbox|
+|destinationBox|string|→|Mailbox to receive moved messages|
+|Result|object|←|Status of the move operation|
+
+#### Description
+
+The `.move()` function moves the messages defined by *msgsIDs* or *allMsgs* to the *destinationBox* on the IMAP server.
+
+You can pass:
+
+* in the *msgsIDs* parameter, a collection containing the unique IDs of the specific messages to move, or
+* in the *allMsgs* parameter, the `kIMAPAll` constant (integer) to move all messages in the selected mailbox.
+
+The *destinationBox* parameter allows you to pass a string value with the name of the mailbox where the messages will be moved.
+
+> This function is only supported by IMAP servers compliant with RFC [8474](https://tools.ietf.org/html/rfc8474).
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||boolean|true if the operation is successful, False otherwise
+|statusText || string|Status message returned by the IMAP server, or last error returned in the Qodly error stack |
+|errors ||collection|4D error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|number| Qodly error code|
+| |\[].message|string|Description of the Qodly error |
+| |\[].componentSignature|string|Signature of the internal component which returned the error|
+
+#### Example 1
+
+To move a selection of messages:
+
+```qs
+ var server,boxInfo,status : object
+ var mailIds : collection
+ var transporter : 4D.IMAPTransporter
+
+ server = newObject()
+ server.host = "imap.gmail.com" //Mandatory
+ server.port = 993
+ server.user = "qodly@gmail.com"
+ server.password = "XXXXXXXX"
+
+ transporter = 4D.IMAPTransporter.new(server)
+
+ //select mailbox
+ boxInfo = transporter.selectBox("inbox")
+
+ //get collection of message unique IDs
+ mailIds = transporter.searchMails("subject \"New feature:\"")
+
+ // Move found messages from the current mailbox to the "documents" mailbox
+ status = transporter.move(mailIds,"documents")
+```
+
+#### Example 2
+
+To move all messages in the current mailbox:
+
+```qs
+ var server,boxInfo,status : object
+ var transporter : 4D.IMAPTransporter
+
+ server = newObject()
+ server.host = "imap.gmail.com" //Mandatory
+ server.port = 993
+ server.user = "4d@gmail.com"
+ server.password = "XXXXXXXX"
+
+ transporter = 4D.IMAPTransporter.new(server)
+
+ //select mailbox
+ boxInfo = transporter.selectBox("inbox")
+
+ // move all messages in the current mailbox to the "documents" mailbox
+ status = transporter.move(kIMAPAll,"documents")
+```
+
+
+
+
+## .numToID()
+
+
+**.numToID**( *startMsg* : integer , *endMsg* : integer ) : collection
+
+
+
+|Parameter|Type||Description|
+|-----|--- |:---:|------|
+|startMsg|integer|→ |Sequence number of the first message|
+|endMsg|integer|→|Sequence number of the last message|
+|Result|collection|←|collection of unique IDs|
+
+#### Description
+
+The `.numToID()` function converts the sequence numbers to IMAP unique IDs for the messages in the sequential range designated by *startMsg* and *endMsg* in the currently selected mailbox.
+
+In the *startMsg* parameter, pass an integer value corresponding to the number of the first message in a sequential range. If you pass a negative number (*startMsg* <= 0), the first message of the mailbox will be used as the beginning of the sequence.
+
+In the *endMsg* parameter, pass an integer value corresponding to the number of the last message to be included in a sequential range. If you pass a negative number (*endMsg* <= 0), the last message of the mailbox will be used as the end of the sequence.
+
+#### Result
+
+The function returns a collection of strings (unique IDs).
+
+#### Example
+
+```qs
+ var transporter : 4D.IMAPTransporter
+ var server,boxInfo,status : object
+ var mailIds : collection
+
+ server = newObject()
+ server.host = "imap.gmail.com" //Mandatory
+ server.port = 993
+ server.user = "qodly@gmail.com"
+ server.password = "XXXXXXXX"
+
+ transporter = 4D.IMAPTransporter.new(server)
+
+ //select mailbox
+ boxInfo = transporter.selectBox("inbox")
+
+ //get IDs for 5 last messages received
+ mailIds = transporter.numToID((boxInfo.mailCount-5),boxInfo.mailCount)
+
+ //delete the messages from the current mailbox
+ status = transporter.delete(mailIds)
+```
+
+
+
+
+## .removeFlags()
+
+
+**.removeFlags**( *msgIDs* : collection , *keywords* : object ) : object **.removeFlags**( *msgIDs* : string , *keywords* : object ) : object **.removeFlags**( *msgIDs* : integer , *keywords* : object ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|msgIDs|collection|→|Collection of strings: Message unique IDs (string)|
+|msgIDs|string| Unique ID of a message|
+|msgIDs|integer|kIMAPAll: All messages in the selected mailbox|
+|keywords|object|→|Keyword flags to remove|
+|Result|object|←|Status of the removeFlags operation|
+
+#### Description
+
+The `.removeFlags()` function removes flags from the *msgIDs* for the specified `keywords`.
+
+In the *msgIDs* parameter, you can pass either:
+
+* a *collection* containing the unique IDs of specific messages or
+* the unique ID (*string*) of a single message or
+* the following constant (*integer*) for all messages in the selected mailbox:
+
+ |Constant |Value |Comment|
+ |---|---|---|
+ |kIMAPAll |1 |Select all messages in the selected mailbox|
+
+The `keywords` parameter lets you define the flags to remove from *msgIDs*. You can use the following standard flags as well as custom flags:
+
+|Parameter|Type|Description|
+|---|---|---|
+|$draft |boolean |true to remove the "draft" flag from the message |
+|$seen |boolean |true to remove the "seen" flag from the message|
+|$flagged |boolean |true to remove the "flagged" flag from the message|
+|$answered |boolean |true to remove the "answered" flag from the message|
+|$deleted |boolean | true to remove the "deleted" flag from the message|
+|`` |boolean | true to remove the custom flag from the message|
+
+Please refer to [.addFlags()](#addflags) for more information on custom flags.
+
+>* For a keyword to be taken into account it has to be true.
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||boolean|true if the operation is successful, False otherwise
+|statusText || string|Status message returned by the IMAP server, or last error returned in the Qodly error stack |
+|errors ||collection|Qodly error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| Qodly error code|
+| |\[].message|string|Description of the Qodly error |
+| |\[].componentSignature|string|Signature of the internal component which returned the error|
+
+#### Example
+
+```qs
+var options,boxInfo,status : object
+var transporter : 4D.IMAPTransporter
+
+options = newObject()
+options.host = "imap.gmail.com"
+options.port = 993
+options.user = "qodly@gmail.com"
+options.password = "xxxxx"
+
+// Create transporter
+transporter = 4D.IMAPTransporter.new(options)
+
+// Select mailbox
+boxInfo = transporter.selectBox("INBOX")
+
+// Mark all messages from INBOX as unseen
+flags = newObject
+flags["$seen"] = true
+status = transporter.removeFlags(kIMAPAll,flags)
+```
+
+
+
+
+## .renameBox()
+
+
+**.renameBox**( *currentName* : string , *newName* : string ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|currentName|string|→|Name of the current mailbox|
+|newName|string|→|New mailbox name|
+|Result|object|←|Status of the renaming operation|
+
+#### Description
+
+The `.renameBox()` function changes the name of a mailbox on the IMAP server. Attempting to rename a mailbox from a mailbox name that does not exist or to a mailbox name that already exists will generate an error.
+
+In the `currentName` parameter, pass the name of the mailbox to be renamed.
+
+Pass the new name for the mailbox in the `newName` parameter.
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||boolean|true if the operation is successful, False otherwise
+|statusText || string|Status message returned by the IMAP server, or last error returned in the Qodly error stack |
+|errors ||collection|Qodly error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| Qodly error code|
+| |\[].message|string|Description of the Qodly error |
+| |\[].componentSignature|string|Signature of the internal component which returned the error|
+
+#### Example
+
+To to rename your "Invoices mailbox to "Bills":
+
+```qs
+var options, status : object
+var transporter : 4D.IMAPTransporter
+var info : string
+
+options = newObject()
+
+options.host = "imap.gmail.com"
+options.user = "test@gmail.com"
+options.password = "XXXXX"
+
+transporter = 4D.IMAPTransporter.new(options)
+
+// rename mailbox
+status = transporter.renameBox("Invoices", "Bills")
+
+if(status.success)
+ info = "Mailbox renaming successful!"
+else
+ info = "Error: "+status.statusText
+end
+```
+
+
+
+
+
+
+## .searchMails()
+
+**.searchMails**( *searchCriteria* : string ) : collection
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|searchCriteria|string|→ |Search criteria|
+|Result|collection|←|collection of message numbers|
+
+#### Description
+
+> This function is based upon the specification for the [IMAP protocol](https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol).
+
+The `.searchMails()` function searches for messages that match the given *searchCriteria* in the current mailbox. *searchCriteria* consists of one or more search keys.
+
+*searchCriteria* is a string parameter listing one or more search keys (see [Authorized search-keys](#authorized-search-keys) below) associated or not with values to look for. A search key may be a single or multiple items. For example:
+
+```
+SearchKey1 = FLAGGED
+SearchKey2 = NOT FLAGGED
+SearchKey3 = FLAGGED DRAFT
+```
+
+> Matching is usually not case-sensitive
+
+* If the *searchCriteria* is a null string, the search will be equivalent to a “select all”.
+* If the *searchCriteria* includes multiple search keys, the result is the intersection (AND function) of all the messages that match those keys.
+
+```
+searchCriteria = FLAGGED FROM "SMITH"
+```
+
+... returns all messages with \Flagged flag set AND sent by Smith.
+
+* You can use the **OR** or **NOT** operators as follows:
+
+```
+searchCriteria = OR SEEN FLAGGED
+```
+
+... returns all messages with \Seen flag set OR \Flagged flag set
+
+```
+searchCriteria = NOT SEEN
+```
+
+... returns all messages with \Seen flag not set.
+
+```
+searchCriteria = HEADER CONTENT-TYPE "MIXED" NOT HEADER CONTENT-TYPE "TEXT"...
+```
+
+... returns message whose content-type header contains “Mixed” and does not contain “string”.
+
+```
+searchCriteria = HEADER CONTENT-TYPE "E" NOT SUBJECT "o" NOT HEADER CONTENT-TYPE "MIXED"
+```
+
+... returns message whose content-type header contains “ e ” and whose Subject header does not contain “ o ” and whose content-type header is not “ Mixed ”.
+
+As concerns the last two examples, notice that the result of the search is different when you remove the parentheses of the first search key list.
+
+* The *searchCriteria* may include the optional \[CHARSET] specification. This consists of the "CHARSET" word followed by a registered \[CHARSET] (US ASCII, ISO-8859). It indicates the charset of the *searchCriteria* string. Therefore, you must convert the *searchCriteria* string into the specified charset if you use the \[CHARSET] specification (see the `CONVERT FROM TEXT` or `Convert to string` commands).
+By default, 4D encodes in Quotable Printable the searchCriteria string if it contains extended characters.
+
+```
+searchCriteria = CHARSET "ISO-8859" BODY "Help"
+```
+
+... means the search criteria uses the charset iso-8859 and the server will have to convert the search criteria before searching, if necessary.
+
+#### Search value types
+
+Search-keys may request the value to search for:
+
+* **Search-keys with a date value**: the date is a string that must be formatted as follows: *date-day+"-"+date-month+"-"+date-year* where date-day indicates the number of the day of the month (max. 2 characters), date-month indicates the name of the month (Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Dec) and date-year indicates the year (4 characters).
+Example: `searchCriteria = SENTBEFORE 1-Feb-2020` (a date does not usually need to be quoted since it does not contain any special characters)
+
+* **Search-keys with a string value**: the string may contain any character and must be quoted. If the string does not contain any special characters, like the space character for instance, it does not need to be quoted. Quoting such strings will ensure that your string value will be correctly interpreted.
+Example: `searchCriteria = FROM "SMITH"`
+For all search keys that use strings, a message matches the key if the string is a substring of the field. Matching is not case-sensitive.
+
+* **Search-keys with a field-name value**: the field-name is the name of a header field.
+Example: `searchCriteria = HEADER CONTENT-TYPE "MIXED"`
+
+* **Search-keys with a flag value**: the flag may accept one or several keywords (including standard flags), separated by spaces.
+Example: `searchCriteria = KEYWORD \Flagged \Draft`
+
+* **Search-keys with a message set value**: Identifies a set of messages. For message sequence numbers, these are consecutive numbers from 1 to the total number of messages in the mailbox. A comma delimits individual numbers, a colon delimits between two numbers inclusive.
+Examples:
+`2,4:7,9,12:*` is `2,4,5,6,7,9,12,13,14,15` for a mailbox with 15 messages.
+`searchCriteria = 1:5 ANSWERED` search in message selection from message sequence number 1 to 5 for messages which have the \Answered flag set.
+`searchCriteria = 2,4 ANSWERED` search in the message selection (message numbers 2 and 4) for messages which have the \Answered flag set.
+
+#### Authorized search-keys
+
+**ALL**: All messages in the mailbox.
+**ANSWERED**: Messages with the \Answered flag set.
+**UNANSWERED**: Messages that do not have the \Answered flag set.
+**DELETED**: Messages with the \Deleted flag set.
+**UNDELETED**: Messages that do not have the \Deleted flag set.
+**DRAFT**: Messages with the \Draft flag set.
+**UNDRAFT**: Messages that do not have the \Draft flag set.
+**FLAGGED**: Messages with the \Flagged flag set.
+**UNFLAGGED**: Messages that do not have the \Flagged flag set.
+**RECENT**: Messages that have the \Recent flag set.
+**OLD**: Messages that do not have the \Recent flag set.
+**SEEN**: Messages that have the \Seen flag set.
+**UNSEEN**: Messages that do not have the \Seen flag set.
+**NEW**: Messages that have the \Recent flag set but not the \Seen flag. This is functionally equivalent to “(RECENT UNSEEN)”.
+**KEYWORD *flag***: Messages with the specified keyword set.
+**UNKEYWORD *flag***: Messages that do not have the specified keyword set.
+**BEFORE *date***: Messages whose internal date is earlier than the specified date.
+**ON *date***: Messages whose internal date is within the specified date.
+**SINCE *date***: Messages whose internal date is within or later than the specified date.
+**SENTBEFORE *date***: Messages whose Date header is earlier than the specified date.
+**SENTON *date***: Messages whose Date header is within the specified date.
+**SENTSINCE *date***: Messages whose Date header is within or later than the specified date.
+**TO *string***: Messages that contain the specified string in the TO header.
+**FROM *string***: Messages that contain the specified string in the FROM header.
+**CC *string***: Messages that contain the specified string in the CC header.
+**BCC *string***: Messages that contain the specified string in the BCC header.
+**SUBJECT *string***: Messages that contain the specified string in the Subject header.
+**BODY *string***: Messages that contain the specified string in the message body.
+**TEXT *string***: Messages that contain the specified string in the header or in the message body.
+**HEADER *field-name* *string***: Messages that have a header with the specified field-name and that contain the specified string in the field-body.
+**UID *message-UID***: Messages with unique identifiers corresponding to the specified unique identifier set.
+**LARGER *n***: Messages with a size larger than the specified number of bytes.
+**SMALLER *n***: Messages with a size smaller than the specified number of bytes.
+**NOT *search-key***: Messages that do not match the specified search key.
+**OR *search-key1* *search-key2***: Messages that match either search key.
+
+
+
+
+## .selectBox()
+
+
+**.selectBox**( *name* : string \{ , *state* : integer \} ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|name|string|→ |Name of the mailbox|
+|state|integer|→|Mailbox access status|
+|Result|object|←|boxInfo object|
+
+#### Description
+
+The `.selectBox()` function selects the *name* mailbox as the current mailbox. This function allows you to retrieve information about the mailbox.
+
+>To get the information from a mailbox without changing the current mailbox, use [`.getBoxInfo()`](#getboxinfo).
+
+In the *name* parameter, pass the name of the mailbox to access. The name represents an unambiguous left-to-right hierarchy with levels separated by a specific delimiter character. The delimiter can be found with the [`.getDelimiter()`](#getdelimiter) function.
+
+The optional *state* parameter defines the type of access to the mailbox. The possible values are:
+
+|Constant| Value| Comment|
+|---|---|---|
+|IMAP read only state|1|The selected mailbox is accessed with read only privileges. Messages with a "recent" flag (indicating new messages) remain unchanged.|
+|IMAP read write state|0|The selected mailbox is accessed with read and write privileges. Messages are considered "seen" and lose the "recent" flag (indicating new messages). (Default value)|
+
+>* The function generates an error and returns **Null** if *name* designates a non-existing mailbox.
+>* If there is no open connection, `.selectBox()` will open a connection.
+>* If the connection has not been used since the designated connection delay (see `4D.IMAPTransporter.new`), the [`.checkConnection()`](#checkconnection) function is automatically called.
+
+**Returned object**
+
+The `boxInfo` object returned contains the following properties:
+
+|Property | Type | Description |
+|---|---|---|
+|name| string|Name of the mailbox|
+|mailCount|number|Number of messages in the mailbox|
+|mailRecent|number|Number of messages with the "recent" flag |
+|id|string|Unique id of the mailbox |
+|flags|string|List of flags currently used for the mailbox, separated by spaces|
+|permanentFlags|string|List of flags that the client can change permanently (except for the \Recent flag, which is managed by the IMAP server), separated by spaces|
+
+:::info
+
+If `permanentFlags` string includes the special flag \*, it means that the server supports [custom flags](#addflags).
+
+:::
+
+#### Example
+
+```qs
+ var server, boxinfo : object
+ var transporter : 4D.IMAPTransporter
+ server = newObject()
+ server.host = "imap.gmail.com" //Mandatory
+ server.user = "4d@gmail.com"
+ server.password = "XXXXXXXX"
+
+ transporter = 4D.IMAPTransporter.new(server)
+ boxInfo = transporter.selectBox("INBOX")
+```
+
+
+
+
+## .subscribe()
+
+**.subscribe**( *name* : string ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|name|string|→ |Name of the mailbox|
+|Result|object|←|Status of the subscribe operation|
+
+#### Description
+
+The `.subscribe()` function allows adding or removing of the specified mailbox to/from the IMAP server’s set of “subscribed” mailboxes. As such, you can choose to narrow down a large list of available mailboxes by subscribing to those you usually want to see.
+
+In the *name* parameter, pass the name of the mailbox to add (subscribe) to your "subscribed" mailboxes.
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||boolean|true if the operation is successful, False otherwise
+|statusText || string|Status message returned by the IMAP server, or last error returned in the Qodly error stack |
+|errors ||collection|Qodly error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| Qodly error code|
+| |\[].message|string|Description of the Qodly error |
+| |\[].componentSignature|string|Signature of the internal component which returned the error|
+
+#### Example
+
+To subscribe to the "Atlas Corp" mailbox in the "Bills" hierarchy:
+
+```qs
+var name, info : string
+var options, status : object
+var transporter : 4D.IMAPTransporter
+
+options = newObject()
+
+options.host = "imap.gmail.com"
+options.user = "test@gmail.com"
+options.password = "XXXXXX"
+
+transporter = 4D.IMAPTransporter.new(options)
+
+name = "Bills"+transporter.getDelimiter()+"Atlas Corp"
+status = transporter.subscribe(name)
+
+if(status.success)
+ info = "Mailbox subscription successful!"
+else
+ info = "Error: "+status.statusText
+end
+```
+
+
+
+
+## .unsubscribe()
+
+
+**.unsubscribe**( *name* : string ) : object
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|name|string|→ |Name of the mailbox|
+|Result|object|←|Status of the unsubscribe operation|
+
+#### Description
+
+The `.unsubscribe()` function removes a mailbox from a set of subscribed mailboxes. This allows you reduce the number of mailboxes you usually see.
+
+In the `name` parameter, pass the name of the mailbox to remove (unsubscribe) from your active mailboxes.
+
+**Returned object**
+
+The function returns an object describing the IMAP status:
+
+|Property|| Type| Description|
+|---|---|---|---|
+|success||boolean|true if the operation is successful, False otherwise
+|statusText || string|Status message returned by the IMAP server, or last error returned in the Qodly error stack |
+|errors ||collection|Qodly error stack (not returned if a IMAP server response is received)|
+| |\[].errcode|Number| Qodly error code|
+| |\[].message|string|Description of the Qodly error |
+| |\[].componentSignature|string|Signature of the internal component which returned the error|
+
+#### Example
+
+To unsubscribe from the "Atlas Corp” mailbox in the "Bills" hierarchy:
+
+```qs
+var info, name : string
+var options, status : object
+var transporter : 4D.IMAPTransporter
+
+options = newObject()
+
+
+options.host = "imap.gmail.com"
+options.user = "test@gmail.com"
+options.password = pw
+
+transporter = 4D.IMAPTransporter.new(options)
+
+name = "Bills"+transporter.getDelimiter()+"Atlas Corp"
+status = transporter.unsubscribe(name)
+
+if(status.success)
+ info = "Mailbox unsubscription successful!"
+else
+ info = "Error: "+status.statusText
+end
+```
+
+
+
+
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/IncomingMessageClass.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/IncomingMessageClass.md
new file mode 100644
index 000000000..a4f3c3de6
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/IncomingMessageClass.md
@@ -0,0 +1,349 @@
+---
+id: IncomingMessageClass
+title: IncomingMessage
+---
+
+
+The `4D.IncomingMessage` class allows you to handle the object received by a custom [**HTTP request handler**](../../4DQodlyPro/httpHandlers.md). HTTP requests and their properties are automatically received as an instance of the `4D.IncomingMessage` class. Parameters given directly in the request with GET verb are handled by the [`.urlQuery`](#urlquery) property, while parameters passed in the body of the request are available through functions such as [`.getBlob()`](#getblob) or [`getText()`](#gettext).
+
+The HTTP request handler can return any value (or nothing). It usually returns an instance of the [`4D.OutgoingMessage`](OutgoingMessageClass.md) class.
+
+All properties of this class are read-only. They are automatically filled by the request handler.
+
+
+### Example
+
+The following HTTP handler configuration [has been defined in Qodly Studio](../../4DQodlyPro/httpHandlers.md#configuring-http-handlers):
+
+```json
+[
+ {
+ "class": "GeneralHandling",
+ "method": "gettingStarted",
+ "pattern": "start",
+ "verbs": "get, post"
+ }
+]
+```
+
+
+The `/start/example?param=demo&name=Qodly` request is run with a `GET` verb in a browser. It is handled by the *gettingStarted* function of the following *GeneralHandling* singleton class:
+
+```qs
+shared singleton constructor()
+
+function gettingStarted(request : 4D.IncomingMessage) : 4D.OutgoingMessage
+
+ var result=4D.OutgoingMessage.new()
+ var body : string
+
+ body="Called URL: "+request.url+"\n"
+
+ body+="The parameters are received as an object: \n"+jsonStringify(request.urlQuery, *)+"\n"
+
+ body+="The verb is: "+request.verb+"\n"
+
+ body+="There are "+string(request.urlPath.length)+" url parts - Url parts are: "\
+ +request.urlPath.join(" - ")+"\n\n"
+
+
+ result.setBody(body)
+ result.setHeader("Content-Type", "text/plain")
+
+ return result
+
+```
+
+The request is received on the server as *request*, an object instance of the 4D.IncomingMessage class.
+
+Here is the response:
+
+```json
+Called URL: /start/example? param=demo&name=Qodly
+The parameters are received as an object:
+{
+ "param": "demo",
+ "name": "Qodly"
+}
+The verb is: GET
+There are 2 url parts - Url parts are: start - example
+```
+
+
+### IncomingMessage Object
+
+
+4D.IncomingMessage objects provide the following properties and functions:
+
+||
+|---|
+|[](#getblob) |
+|[](#getheader) |
+|[](#getjson) |
+|[](#getpicture) |
+|[](#gettext) |
+|[](#headers) |
+|[](#url) |
+|[](#urlpath) |
+|[](#urlquery) |
+|[](#verb) |
+
+:::note
+
+A 4D.IncomingMessage object is a [non-sharable](./basics/lang-shared.md) object.
+
+:::
+
+
+
+## .getBlob()
+
+**.getBlob**() : blob
+
+
+|Parameter|Type||Description|
+|---|--- |---|------|
+|Result|blob|←|Body of the request as a blob|
+
+
+#### Description
+
+The `.getBlob()` function returns the body of the request as a blob.
+
+If the body has not been given as a binary content, the function tries to convert the value but it can give unexpected results.
+
+
+
+
+
+
+## .getHeader()
+
+**.getHeader**( *key* : string ) : string
+
+
+|Parameter|Type||Description|
+|---|--- |---|------|
+|key|string|→|Header property to get|
+|Result|string|←|Value of the header property|
+
+
+#### Description
+
+The `.getHeader()` function returns the value of the *key* header.
+
+:::note
+
+The *key* parameter is not case sensitive.
+
+:::
+
+#### Example
+
+```qs
+var value : string
+var request : 4D.IncomingMessage
+value = request.getHeader("content-type")
+```
+
+
+
+
+
+## .getJSON()
+
+**.getJSON**() : variant
+
+
+|Parameter|Type||Description|
+|---|--- |---|------|
+|Result|variant|←|JSON resolution of the body of the request|
+
+
+#### Description
+
+The `.getJSON()` function returns the body of the request as a JSON resolution.
+
+If the body has not been given as JSON valid content, an error is raised.
+
+
+
+
+
+## .getPicture()
+
+**.getPicture**() : picture
+
+
+|Parameter|Type||Description|
+|---|--- |---|------|
+|Result|picture|←|Body of the request as picture|
+
+
+#### Description
+
+The `.getPicture()` function returns the body of the request as a picture (in case of a body sent as a picture).
+
+The content-type must be given in the headers to indicate that the body is a picture.
+
+:::note
+
+If the request is built using the [`HTTPRequest` class](HTTPRequestClass.md), the picture must be sent in the body as a blob with the appropriate content-type.
+
+:::
+
+If the body is not received as a valid picture, the function returns null.
+
+
+
+
+
+
+
+## .getText()
+
+**.getText**() : string
+
+
+|Parameter|Type||Description|
+|---|--- |---|------|
+|Result|string|←|Body of the request as text|
+
+
+#### Description
+
+The `.getText()` function returns the body of the request as a text value.
+
+If the body has not been given as a string value, the function tries to convert the value but it can give unexpected results.
+
+
+
+
+
+
+
+
+
+## .headers
+
+**headers** : object
+
+#### Description
+
+The `.headers` property contains the current headers of the incoming message as key/value pairs (strings).
+
+The `.headers` property is read-only.
+
+Header names (keys) are lowercased. Note header names are case sensitive.
+
+
+
+
+
+
+
+## .url
+
+**url** : string
+
+#### Description
+
+The `.url` property contains the URL of the request without the *IP:port* part and as a string.
+
+For example, if the request is addressed to: "http://www.mywebapp/docs/invoices/today", the `.url` property is "/docs/invoices/today".
+
+The `.url` property is read-only.
+
+:::note
+
+The "host" part of the request (*IP:port*) is provided by the [`host` header](#headers).
+
+:::
+
+
+
+
+## .urlPath
+
+**urlPath** : collection
+
+#### Description
+
+The `.urlPath` property contains the URL of the request without the *IP:port* part and as a collection of strings.
+
+For example, if the request is addressed to: "http://www.mywebapp/docs/invoices/today", the `.urlPath` property is ["docs", "invoices" ,"today"].
+
+The `.urlPath` property is read-only.
+
+
+
+
+
+
+## .urlQuery
+
+**urlQuery** : object
+
+#### Description
+
+The `.urlQuery` property contains the parameters of the request when they have been given in the URL as key/value pairs.
+
+The `.urlQuery` property is read-only.
+
+
+Parameters can be passed in the URL of requests **directly** or **as JSON contents**.
+
+#### Direct parameters
+
+Example: `http://www.mywebapp.com/myCall?firstname=Marie&id=2&isWoman=true`
+
+In this case, parameters are received as stringified values in the `urlQuery` property: `urlQuery = {"firstname":"Marie" ,"id":"2" ,"isWoman":"true"}`
+
+
+#### JSON contents parameters
+
+Example: `http://www.mywebapp.com/myCall/?myparams='[{"firstname": "Marie","isWoman": true,"id": 3}]'`.
+
+Parameters are passed in JSON format and enclosed within a collection.
+
+In this case, parameters are received as JSON text in the `urlQuery` property and can be parsed using [`jsonParse`](../qodlyScript/commands/jsonParse.md).
+
+```qs
+//urlQuery.myparams: "[{"firstname": "Marie","isWoman": true,"id": 3}]"
+test = valueType(jsonParse(r.urlQuery.myparams)) == kCollection) //true
+```
+
+Special characters such as simple quotes or carriage returns must be escaped.
+
+Example: `http://www.mywebapp.com/syntax/?mdcode=%60%60%60qodly`
+
+```4d
+//urlQuery.mdcode : ```qodly
+test = length(r.urlQuery.mdcode) //8
+```
+
+:::note
+
+Parameters given in the body of the request using POST or PUT verbs are handled through dedicated functions: [`getText()`](#gettext), [`getPicture()`](#getpicture), [`getBlob()`](#getblob), [`getJSON()`](#getjson).
+
+:::
+
+
+
+
+
+
+## .verb
+
+**verb** : string
+
+#### Description
+
+The `.verb` property contains the verb used by the request.
+
+HTTP and HTTPS request verbs include for example "get", "post", "put", etc.
+
+The `.verb` property is read-only.
+
+
+
diff --git a/versioned_docs/version-21/QodlyinCloud/qodlyScript/MailAttachmentClass.md b/versioned_docs/version-21/QodlyinCloud/qodlyScript/MailAttachmentClass.md
new file mode 100644
index 000000000..165c1a674
--- /dev/null
+++ b/versioned_docs/version-21/QodlyinCloud/qodlyScript/MailAttachmentClass.md
@@ -0,0 +1,213 @@
+---
+id: MailAttachmentClass
+title: MailAttachment
+---
+
+Attachment objects allow referencing files within a [`Email`](EmailObjectClass) object.
+
+
+### Functions and properties
+
+Attachment objects are created using the [`4D.MailAttachment.new()`](#4dmailattachmentnew) function. They provide the following functions and read-only properties:
+
+||
+|---|
+|[](#4dmailattachmentnew) |
+|[](#cid) |
+|[](#disposition) |
+|[](#getcontent) |
+|[](#name) |
+|[](#path) |
+|[](#platformpath) |
+|[](#type) |
+
+
+## 4D.MailAttachment.new()
+
+**4D.MailAttachment.new**( *file* : 4D.File \{ , *name* : string \{, *cid* : string\{ , *type* : string \{ , *disposition* : string \} \} \} \} ) : 4D.MailAttachment **4D.MailAttachment.new**( *zipFile* : 4D.ZipFile \{ , *name* : string \{, *cid* : string\{ , *type* : string \{ , *disposition* :string \} \} \} \} ) : 4D.MailAttachment **4D.MailAttachment.new**( *blob* : 4D.Blob \{ , *name* : string \{, *cid* : string\{ , *type* : string \{ , *disposition* :string \} \} \} \} ) : 4D.MailAttachment **4D.MailAttachment.new**( *path* : string \{ , *name* : string \{, *cid* : string\{ , *type* : string \{ , *disposition* :string \} \} \} \} ) : 4D.MailAttachment
+
+
+
+
+|Parameter|Type||Description|
+|---------|--- |:---:|------|
+|file|4D.File|→|Attachment file|
+|zipFile|4D.ZipFile|→|Attachment Zipfile|
+|blob|4D.Blob|→|BLOB containing the attachment|
+|path|string|→|Path of the attachment file|
+|name|string|→|Name + extension used by the mail client to designate the attachment|
+|cid|string|→|ID of attachment (HTML messages only), or " " if no cid is required|
+|type|string|→|Value of the content-type header|
+|disposition|string|→|Value of the content-disposition header: "inline" or "attachment".|
+|Result|4D.MailAttachment|←|Attachment object|
+
+
+#### Description
+
+The `4D.MailAttachment.new` function allows you to create an attachment object that you can add to an [Email object](EmailObjectClass.md#properties).
+
+To define the attachment, you can use:
+
+- a *file*, pass a `4D.File` object containing the attachment file.
+- a *zipfile*, pass a `4D.ZipFile` object containing the attachment file.
+- a *blob*, pass a `4D.Blob` object containing the attachment itself.
+- a *path*, pass a **string** value containing the path of the attachment file, expressed with the system syntax. You can pass a full path name or a simple file name (in which case 4D will search for the file in the same directory as the project file).
+
+The optional *name* parameter lets you pass the name and extension to be used by the mail client to designate the attachment. If *name* is omitted and:
+
+* you passed a file path, the name and extension of the file is used,
+* you passed a BLOB, a random name without extension is automatically generated.
+
+The optional *cid* parameter lets you pass an internal ID for the attachment. This ID is the value of the `Content-Id` header, it will be used in HTML messages only. The cid associates the attachment with a reference defined in the message body using an HTML tag such as `\`. This means that the contents of the attachment (e.g., a picture) should be displayed within the message on the mail client. The final result may vary depending on the mail client. You can pass an empty string in *cid* if you do not want to use this parameter.
+
+You can use the optional *type* parameter to explicitly set the `content-type` of the attachment file. For example, you can pass a string defining a MIME type ("video/mpeg"). This content-type value will be set for the attachment, regardless of its extension. For more information about MIME types, please refer to the [MIME type page on Wikipedia](https://en.wikipedia.org/wiki/MIME).
+
+By default, if the *type* parameter is omitted or contains an empty string, the `content-type` of the attachment file is based on its extension. The following rules are applied for the main MIME types:
+
+|Extension| Content Type|
+|---|---|
+|jpg, jpeg| image/jpeg|
+|png| image/png|
+|gif| image/gif|
+|pdf| application/pdf|
+|doc| application/msword|
+|xls| application/vnd.ms-excel|
+|ppt| application/vnd.ms-powerpoint|
+|zip| application/zip|
+|gz| application/gzip|
+|json| application/json|
+|js| application/javascript|
+|ps| application/postscript|
+|xml| application/xml|
+|htm, html| text/html|
+|mp3| audio/mpeg|
+|*other*| application/octet-stream|
+
+The optional *disposition* parameter lets you pass the `content-disposition` header of the attachment. You can pass one of the following constants:
+
+|Constant|Value|Comment|
+|---|---|---|
+|kMailDispositionAttachment|"attachment"|Set the Content-disposition header value to "attachment", which means that the attachment file must be provided as a link in the message.|
+|kMailDispositionInline|"inline"|Set the Content-disposition header value to "inline", which means that the attachment must be rendered within the message contents, at the "cid" location. The rendering depends on the mail client.|
+
+By default, if the *disposition* parameter is omitted:
+
+* if the *cid* parameter is used, the `Content-disposition` header is set to "inline",
+* if the *cid* parameter is not passed or empty, the `Content-disposition` header is set to "attachment".
+
+#### Example
+
+You want to send an email with a file as an attachment and an image embedded in the HTML body:
+
+```qs
+var attachment : 4D.File
+var email,server : object
+var transporter : 4D.SMTPTransporter
+
+attachment = file("/PACKAGE/Docs/cv-john-smith.pdf")
+
+server = newObject
+server.host = "smtp.mail.com"
+server.user = "test_user@mail.com"
+server.password = "p@ssw@rd"
+transporter = 4D.SMTPTransporter.new(server)
+
+email = newObject
+email.from = "test_user@mail.com"
+email.to = "test_user@mail.com"
+email.subject = "This is a test message with attachments"
+
+//add a link to download file
+email.attachments = newCollection(4D.MailAttachment.new(attachment))
+//insert an inline picture (use a cid)
+email.attachments[1] = 4D.MailAttachment.new(file("/PACKAGE/Docs/photo.jpg"),"","Qodly")
+
+email.htmlBody = ""+\
+"Hello World!"+\
+""+\
+""+\
+""+\
+"