Web modules are a special integration mechanism between the Melbis server platform and its Windows client, Melbis Shop. The core idea: a full-featured Chromium-based browser is embedded inside the application, capable of opening website pages directly within the application’s windows and communicating with them in both directions — passing the manager’s current working context to the server and receiving results back. A developer creates a standard Melbis module, and the application opens it inside its own window.
This allows extending the application’s functionality without modifying its code: analytics, reports, management tools — all of this can be implemented in PHP and connected through settings.
Embedded modules work directly inside the application’s working
windows — “Products / Prices”, “Business / Customers”, “Business /
Orders”. To open an embedded web module in any of these windows, press
Ctrl+W — a panel with an embedded browser will appear.
The key feature: when a manager navigates through records (selects a product, opens an order, switches to a customer), the application automatically sends a POST request to the module with the current window’s context. The module always knows which object the manager is currently working with and can display relevant information — without any additional actions on their part.
There can be multiple such modules for each section — they are switched using tabs. Access rights can be configured for each module: which user groups can see it.
Examples of embedded modules for the “Products / Prices” section:
Examples for the “Business / Orders” and “Business / Customers” sections:
Embedded modules are configured in the application: “Development → Modules and Options”, the “Embedded Modules” tab. Select a group (“Products”, “Customer”, “Orders”) and add a module with the following parameters:
http://my-shop.com/?mod=melbis_web_sample.melbis_web_sample). Used for access rights
verification.login and
pass_code (MD5 hash of the password) fields in POST. This
allows the module to authenticate the user and verify their
permissions.With each request to an embedded module, the application sends a POST request with the current window’s context. Below is the full set of parameters for the “Orders” section:
// $mVars in the module:
[
'get' => ['mod' => 'melbis_web_sample'],
'post' => [
'order_id' => '1',
'order_version_id' => '2',
'order_version_client_id' => '1',
'order_version_user_id' => '1',
'order_client_field_id' => '3',
'order_store_id' => '2',
'order_option_id' => '3',
'login' => 'admin',
'pass_code' => '21232f297a57a5a743894a0e4a801fc3',
]
]In the “Products” section, the store_id of the selected
product is passed. In the “Customers” section — the client identifier.
The password is passed as an MD5 hash.
For web modules, the standard demo store distribution includes the
melbis_inc_auth library. It handles all the boilerplate
code: user authentication via login +
pass_code, storing the authentication state in a PHP
session, checking access rights to the module, and routing POST calls
between module functions.
The central function —
MELBIS_INC_AUTH\Router($mModule, $mVars) —
performs the following steps in order:
logout in
POST).login +
pass_code from POST, or from a previously saved
session.GlobalAssign('PAGE', [...]), available in all templates:
authentication status (auth), user_id, and
mod — the URL of the current module. The
{PAGE:MOD} variable is used in templates as the base URL
for AJAX requests.func parameter is passed in
POST — calls the module function with that name
(GetCataloge), passing it $userId and
$mVars. Without func, Page is
called. Any function other than Page works only after login
and with the right to the module — otherwise the answer is
Access denied.Thanks to this, the entire main module function reduces to a single line:
namespace MELBIS_WEB_SAMPLE;
use MELBIS_INC_AUTH as AUTH;
function Main($mVars)
{
return AUTH\Router(MELBIS()->UnitName(), $mVars);
}MELBIS()->UnitName() returns the name of the current
module — it is passed to the router for rights verification and for
finding the module’s functions.
The function name arrives as data, from POST, so the router calls it
through UnitFunc: it assembles the full name from the
running module — MELBIS_WEB_SAMPLE\GetCataloge, and in a
module of the former form, without a namespace,
MELBIS_WEB_SAMPLE_GetCataloge. One and the same router
serves both forms.
The Page function outputs the module’s page: the router
calls it when func is not passed. It is the only function
the router calls even without login — it is also the one that shows the
authentication form. It receives $mUserId (or
null if no login took place) and the full
$mVars array.
function Page($mUserId, $mVars)
{
$tpl = MELBIS()->TplCreate();
if ( $mUserId > 0 )
{
// Pass input variables to the template
MELBIS()->TplAssign($tpl, 'VARS', var_export($mVars, true));
// Pass order data for the reverse data transfer mechanism
MELBIS()->TplAssign($tpl, 'ORDER', $mVars['post']['order'] ?? '{}');
MELBIS()->TplParse($tpl, 'CONTENT', 'page');
}
else
{
// User is not authenticated — show the login form
MELBIS()->TplAssign($tpl, 'ORDER', '{}');
MELBIS()->TplParse($tpl, 'CONTENT', 'auth');
}
MELBIS()->GlobalAppend('PAGE:TITLE', 'Sample Web module');
return MELBIS()->TplFinal($tpl, 'main');
}The auth.htm template contains a call to the
melbis_web_auth module — it renders the login form. After
the form is submitted, the router AUTH\Router processes the
POST again, authenticates the user, and this time Page
parses the main page.htm template.
For interactive actions within the module, JavaScript passes the
func parameter in POST. The router will find and call the
corresponding function — provided the user is authenticated and has
access to the module.
In the template, the {PAGE:MOD} variable contains the
ready-made URL of the current module
(/?mod=melbis_web_sample) — it is convenient to use as the
endpoint for all AJAX requests:
$('#melbis_table_cataloge').bootstrapTable({
url: '{PAGE:MOD}',
method: 'post',
queryParams: function(params) {
params.func = 'GetCataloge'; // the router will call GetCataloge of this module
return params;
},
pagination: true,
sidePagination: 'server',
pageSize: 20,
sortName: 'absindex',
sortOrder: 'asc'
});On the PHP side, the function receives control only if the user is authenticated, executes the query, and returns JSON:
function GetCataloge($mUserId, $mVars)
{
$limit = (int) $mVars['post']['limit'];
$offset = (int) $mVars['post']['offset'];
$sort = preg_replace('/[^a-z_]/', '', $mVars['post']['sort']);
$sort .= ($mVars['post']['order'] == 'asc') ? ' ASC' : ' DESC';
$command = "SELECT t.id, t.name, t.tlevel, COUNT(ts.id) AS amount
FROM {DBNICK}_topic t
LEFT JOIN {DBNICK}_topic_store ts ON t.id = ts.topic_id
GROUP BY t.id
ORDER BY $sort";
$data = MELBIS()->SqlSelectLimit(__LINE__, $command, $offset, $limit);
return json_encode($data);
}The same principle applies to any other module functions — searching, filtering, saving data to the database, sending notifications, and so on.
The most unique capability of embedded web modules is the ability to pass data from the browser back to the Windows application without additional HTTP requests.
The page sends the application a message with the
window.chrome.webview.postMessage method: an object with
the event name in the event field and the data in the
data field.
window.chrome.webview.postMessage({ event: 'MELBIS_ORDER_UPDATE', data: update });The application understands two events:
| Event | Where it works | What the application does |
|---|---|---|
MELBIS_ORDER_UPDATE |
the order editing window | applies data to the fields of the order being
edited |
MELBIS_PRINT |
any browser of the application | opens the Windows print dialog for the page |
The window.chrome.webview object exists only in the
application’s browser. A module that is also opened in an ordinary
browser checks for it before sending (an example is in the “Printing”
section below).
MELBIS_ORDER_UPDATE works while the order has not yet
been saved. In other sections (products, customers), the web module
writes changes directly to the database via the standard SQL parser
methods when needed.
A practical example: the module receives all the data of the open order from the application in JSON format, displays it in interactive tables, and allows the manager to edit individual fields directly in the browser.
// PHP: retrieve the order data passed by the application and send it to the template
MELBIS()->TplAssign($tpl, 'ORDER', $mVars['post']['order'] ?? '{}');// Template: parse JSON and build tables
var melbis_order = {ORDER};
function melbis_init_back() {
for (var table in melbis_order) {
var data = melbis_order[table];
var columns = [];
for (var c in data[0]) {
columns.push({field: c, title: c});
}
$('.melbis_table_order[data-table="' + table + '"]').bootstrapTable({
columns: columns,
data: data
});
}
}
// Click on a cell — edit dialog
$('.melbis_table_order').on('click-cell.bs.table', function(event, field, value, row) {
var table = event.target.dataset.table;
bootbox.prompt({
title: 'Edit value of "' + field + '"',
value: value,
callback: function(result) {
if (result != null) {
var update = {};
update[table] = [{id: row.id, [field]: result}];
// Pass the change to the application
window.chrome.webview.postMessage({ event: 'MELBIS_ORDER_UPDATE', data: update });
}
}
});
});The application receives the MELBIS_ORDER_UPDATE event
and applies the changes to the order fields in its interface.
The MELBIS_PRINT event opens the classic Windows print
dialog for the page: the choice of printer, number of copies and pages.
The standard window.print() shows the browser’s print
preview inside the module panel itself instead.
A print button that works both in the application and in an ordinary browser:
function melbis_print() {
if (window.chrome && window.chrome.webview) {
window.chrome.webview.postMessage({ event: 'MELBIS_PRINT' });
} else {
window.print();
}
}External modules differ from embedded ones in one fundamental way:
they are not tied to a specific application window and do not receive
the current record’s context. Instead, when opened from the application,
only the user’s authentication parameters (login,
pass_code) are passed.
Access to external modules is available through the “Business / Web Modules” section — this contains a browser with a catalog of all available modules in the left panel. In addition, any of these modules can be opened in a regular browser on any device — a tablet, smartphone, or a workstation without the application installed.
This makes external modules a universal tool for dashboards, analytical reports, and administrative panels — anything that does not require binding to a specific product or order. The screenshot above shows an example of a real external module with sales analytics, financial accounting, inventory analysis, and management tools built directly into the application.
Configuring external modules — “Development → Modules and Options”, the “External Web Modules” tab: a name, URL, and symbolic key are set for each module, along with access rights by user groups.
Since external modules can also be opened from a regular browser, authentication here works in two modes automatically:
When opened from the application — Melbis Shop
passes login and pass_code in POST.
AUTH\Router authenticates the user without a form and
immediately displays the content.
When opened directly in a browser — POST does not
contain credentials, the router passes $mUserId = null, and
the Page function substitutes the authentication form
template. After a successful login, the data is saved in the PHP
session, and the form is no longer shown on subsequent requests.
The same module code works in both cases — the branching happens
entirely automatically inside the melbis_inc_auth library.
The developer only needs to correctly handle the
$mUserId == null case in the Page
function.
Windows Melbis Shop
│
│ POST: store_id / order_id / login / pass_code
▼
index.php → Run('melbis_web_sample')
│
▼
MELBIS_WEB_SAMPLE\Main($mVars)
│
▼
MELBIS_INC_AUTH\Router(...)
├─ authentication + rights verification
├─ func absent → MELBIS_WEB_SAMPLE\Page($userId, $mVars) → HTML
├─ func = 'GetCataloge' → MELBIS_WEB_SAMPLE\GetCataloge($userId, $mVars) → JSON
└─ func = 'GetGoods' → MELBIS_WEB_SAMPLE\GetGoods($userId, $mVars) → JSON
HTML page in the application's browser
│ (only for the order editing window)
▼
postMessage({ event: 'MELBIS_ORDER_UPDATE', data: {"version":[...]} })
│
▼
Windows Melbis Shop — applies changes to the order