TeamWox SDK: Setting Up Custom Modules Environment - Part 1

Introduction

You've probably noticed that all modules from standard TeamWox delivery share the same style of user interface. All modules have similar controls, that eventually develop a sense of system integrity.

From the previous articles you've already got some experience of developing custom modules. In this article we will talk about how to properly configure the environment of custom modules for their further distribution. As an example we will use the Hello World training module.  

In particular, you will learn how to use the structure with information about the module, how templates and resource files are processed, how to create help files, how to add commands on the main page. In the second part we will talk about widgets and the Tips system module.

1. Setting Up the Environment
    1.1. Getting Information About Module
    1.2. Getting Module Interface
2. Resources and Templates
    2.1. Resources
    2.2. Templates
3. Help Files
    3.1. Files and Folders Structure
    3.2. Topics and Pages Order
4. Integration of User Commands in TeamWox Modules
    4.1. User Commands in TeamWox Main Page Header
    4.2. Downloading Module Scripts in Topmost Frame

1. Setting Up the Environment

DLL of every TeamWox module must always export the following methods:

  1. ModuleGetInfo(int index, ModuleInfo *info). This method retrieves information about the module by filling out the ModuleInfo structure. This information will be used, when the new module is added and registered in TeamWox.
  2. ModuleGetInterface(int id, int server_api_version, IModule **module). This method returns the IModule interface, in order for server to communicate with the module.  

1.1. Getting Information About Module

The ModuleInfo structure

Let's consider this structure, containing all the necessary information about a module.

Field
Type
Description
id
int
Unique identifier of the module. The first 65535 digits are reserved for modules from TeamWox standard delivery. Custom modules must have IDs greater than 65535.
version
int
Module version. The integer value will be displayed as floating point number rounded up to two decimal digits. For example, number 100 will be displayed as 1.00.
build
int
Module build number. An important parameter that allows you to perform various checks (like we did in the TeamWox SDK: Interaction with DBMS article). With every new module build developers have to increment this parameter.
build_date
wchar_t[16]  
Date of module build. Informational line.
version_api
unsigned int
TeamWox API version, using which the module has been compiled. Checked in the ModuleGetInterface method against the current version of server API. If TeamWox API version, used to compile the module, is greater (newer) than the server version - the module simply won't be loaded.
name
wchar_t[64]  
  Short name of the module, that is used in HTTP requests. May contain only Latin characters and underscores.
dependences
int[32]   List of modules required for this module. DEPRECATED, functionality is implemented in the IModule::PostInitialize method.
color
wchar_t[7]  
Color of page header gradient fill (6 characters, zero). DEPRECATED, functionality is implemented in templates.
flags
int
Flags of module description. Provided by the EnModuleFlags enumeration (for details see below).
icon_url
wchar_t[260]  
Path to module icon, that is displayed on the Management -> Modules page.
home_site
wchar_t[260]  
Link to the the module manufacturer's website.
reserved
char[256]  
Reserved field.

The id, name, icon_url, home_site and reserved fields generally do not change during module development and are set only once. When module is developed, most actively changing are the build and build_date fields, and periodically the version_api filed - when new version of TeamWox server (and TeamWox API, respectively) is released.

For the Hello World module the ModuleInfo structure is filled out in the CHelloWorldModule::InfoGet(ModuleInfo *info) method.

ModuleInfo module_info=
 {
  HELLOWORLD_MODULE_ID,                                    // HELLOWORLD_MODULE_ID = 65536
  ProgramVersion,                                          // #define ProgramVersion   100
  ProgramBuild,                                            // #define ProgramBuild     100
  ProgramBuildDate,                                        // #define ProgramBuildDate L"12 Oct 2008"
  TEAMWOX_API_VERSION,                                     // #define TEAMWOX_API_VERSION 76
  L"helloworld",                                           // 
  {0},                                                     // 
  L"",                                                     // 
  TW_MODULE_FLAG_DIGITAL_SIGN | TW_MODULE_FLAG_MODULE_TAB, // Module is digitally signed by TeamWox developers 
                                                           // and has its tab on the main page
  L"/helloworld/res/i/logo.gif",                           // Module logo
  L"https://www.teamwox.com/"                               // 
 };          
        

Module name (the name filed) is used to generate path in HTTP request line.

Short Name of the Module

Module logo is displayed when module is turned on in the Administration module on the Modules tab,

Module Logo

as well as in the user profile on the Tabs Order tab.

Module Logo in User Profile


The EnModuleFlags Enumeration

Flags from this enumeration directly affect how the module is displayed in user interface.

Name
Value
Description
TW_MODULE_FLAG_LEFTPAGE
0x01
Indicates the left panel. If the flag is set, the module page will display the left panel, and a Web browser will sends HTTP request like /module_name/left (for more info see below).
TW_MODULE_FLAG_DIGITAL_SIGN
0x02
Module protection with digital signature. If the flag is set, it indicates that module is certified by TeamWox developers (just like modules from standard delivery).
TW_MODULE_FLAG_MODULE_TAB
0x04
Displays the module tab on TeamWox main page.
TW_MODULE_FLAG_HIDDEN
0x08
Module has no visual part, i.e. it integrates with other modules (for example, the Reports module).
TW_MODULE_FLAG_MANUAL_CHECK
0x10
Disable automatic verification of module permissions. If the flag is not set (default) and module is disabled, the server won't send requests. If the flag is set, then requests will be sent in any case (such as in the Reports module).

 

1.2. Getting Module Interfaces

Now, once we've got module information, we need to get module interfaces. Every module class, in order to work in system, must implement the IModule interface. This is the main interface, that is used by server to communicate with module.

Module methods, inherited from the IModule interface, are called by TeamWox server in a certain order.

1. Initialization - Initialize(class IServer *server, int prev_build). Only server's and server modules' interfaces can be called. To ensure system works correctly, at this stage there is no interaction with other modules.

2. Post initialization - PostInitialize(). All modules are loaded, and now other modules' interfaces can be called.

3. Working with the system. This is the main stage, when users interact with the server.

  • Processing request - Process(const Context *context). For any module in TeamWox system there are two reserved URL parts, that are recommended to use in HTTP requests:
    • /module_name/index - Redirecting to the main page of the module.   
    • /module_name/left - Displaying the left panel. This type of request creates the additional frame (with separator) in user interface of a module. This frame will which display the result of this request. In user interface of the left pane, you can apply any controls, but it is recommended to use the List control - this one is used in modules from standard TeamWox delivery.  

    The Left Panel with Separator

  • Listing the necessary interfaces - GetInterface(const wchar_t *name, void **iface). This method must return interfaces, provided and implemented by module.

4. Deinitialization - PrepareDestroy(). Server stops all processing, preparing to end working.

5. Free resources - Release(). Ending work on server shutdown or reboot.

In addition to these main methods, the IModule interface provides several auxiliary methods, that can help you get additional information about the module,

IModule Interface Methods Displaying Information About the Module

as well as to manage module permissions.

IModule Interface Methods Controlling Permissions

2. Resources and Templates

In addition to the compiled source code in form of DLL, module also includes resources (online help, images, scripts, stylesheets, etc.), as well as page templates that define user interface.

2.1. Resources

For every custom module you must configure access to its resources. Processing HTTP requests for static resource files is implemented directly in the server. So there is no need to implement it for every module individually. TeamWox server provides efficient processing of requests for static files - it caches them in memory for quick access.  

In order for server to process HTTP requests for resources, on the first stage of module initialization (IModule::Initialize) it must register the URL and the path to files using the IServer::VirtualPathRegister method.

VirtualPathRegister(const wchar_t *path_virtual, const wchar_t *path_real,int flags)

Параметр
Type
Description
path_virtual
wchar_t*
URL prefix for resource files processing. This path will be processed by the server.
path_real
wchar_t*
Path to resource file or folder with resource files, relative to the folder (on the server) with module DLL.
flags
int
Flags of the EnVirtualFoldersFlags enumeration.

Thus, we bind the actual location of resources with the text of HTTP request line. For the Hello World module registering virtual paths looks as follows.

//--- Mapping folders with static content - images, scripts
  m_server->VirtualPathRegister(L"/res",   L"res",    TW_VIRTUAL_FOLDER_FLAG_CACHE);
//---     JavaScript may contain translation - the TW_VIRTUAL_FOLDER_FLAG_LANGS flag
  m_server->VirtualPathRegister(L"/res/js",L"res\\js",TW_VIRTUAL_FOLDER_FLAG_CACHE | TW_VIRTUAL_FOLDER_FLAG_LANGS);

Now, lets consider the flags that can be set for resources.

EnVirtualFoldersFlags

Name
Value
Description
TW_VIRTUAL_FOLDER_FLAG_CACHE
0x001
The response from server includes the Expires HTTP header, that tells browser to cache file until the expiration date. So when you reload a page, browser won't send request to the server.
TW_VIRTUAL_FOLDER_FLAG_NOCACHE
0x002
The HTTP response includes the Expires header with known past date, so that files are not cached (several flags flags are set in order not to cache data).
TW_VIRTUAL_FOLDER_FLAG_DYNAMIC
0x004
The HTTP response includes the Last-Modified header, and HTTP request includes the If-Modified-Since header. Setting this flag allows you to implement a so-called dynamic caching.
TW_VIRTUAL_FOLDER_FLAG_LANGS
0x008
File may contain <lng:> tokens for translations substitution. Such files are processed by system translations module.
TW_VIRTUAL_FOLDER_NOT_SECURE
0x010

Files can be sent when requested via insecure HTTP protocol.

Note: Don't worry about potential DDoS attacks. For such requests the public part simply won't respond, and this will not affect the system performance.

TW_VIRTUAL_FOLDER_NOT_AUTHORIZE
0x020
Allow to process HTTP requests from unauthorized users.
TW_VIRTUAL_FOLDER_PUBLIC
0x040
Makes resource available for unauthorized users in public requests.
TW_VIRTUAL_FOLDER_SEND_ATTACHMENT
0x080
If the flag is set for an individual file or for an entire folder, the HTTP response header includes the Content-Disposition: attachment header. Thus, a download dialog box will always pop up for submitted files, regardless of the MIME-type.

The most commonly used are the TW_VIRTUAL_FOLDER_FLAG_CACHE flag - to reduce the server load, and the TW_VIRTUAL_FOLDER_FLAG_LANGS flag - to provide multilingual support.

2.2. Templates

Now let's talk about templates. From the previous articles you've already known that template files define the user interface of pages. Template files should reside in a folder relative to the module DLL. For convenience, the template files are grouped in the \templates subfolder and have the *.tpl extension.

For example, when you call the IServer::PageProcess method, that processes template for the PageIndex page, specify the following relative path as the second parameter:

return(server->PageProcess(context, L"templates\\index.tpl", this, TW_PAGEPROCESS_NOCACHE));

3. Help Files

Reference documentation is sine qua non for every high-quality software. In addition, it simplifies the technical support. In TeamWox groupware context sensitive help is displayed by the Help system module (TWX_HELP).

This module initially has the ability to display online documentation in all languages ​​supported in TeamWox (depends on language settings in user profile).

English is the obligatory language for module's online help. It will be used by default if user interface language doesn't have an appropriate translation of reference documentation.

"Help" Module in English "Help" Module in Russian

Help for a particular module is displayed using the corresponding button in page header.

Help Button

You've already learned, that page header is created using the PageHeader control, and the Help button - using its method Help. The Help method parameter is specified in the following format:

module_name/html_file_name

For the Hello World module main page, it looks as follows:

//+----------------------------------------------+
//| Page Header with commands                    |
//+----------------------------------------------+
var header = TeamWox.Control("PageHeader","#41633C")
             .Command("<lngj:MENU_HELLOWORLD_NUMBER1>","/helloworld/number_one","<lngj:MENU_HELLOWORLD_NUMBER1>")
             .Command("<lngj:MENU_HELLOWORLD_NUMBER2>","/helloworld/number_two","<lngj:MENU_HELLOWORLD_NUMBER2>")
             .Command("<lngj:MENU_HELLOWORLD_REPORTS>","/reports/helloworld/helloworld_report","<lngj:MENU_HELLOWORLD_REPORTS>")
             .Help("helloworld/index")
             .Search(65536);

3.1. Files and Folders Structure

Help files are common HTML documents with images and styles, i.e. they refer to module resources. To display help files in the "Help" module, module developers have to create folder structure in the following format.  

module_name\resources_folder\help\language\
  • resources_folder - Registered using the IServer::VirtualPathRegister method (see above). 
  • help - Reserved name for the folder, processed by the "Help" module.
  • language - Three-letter name of the folder, that corresponds to the language code. Language code (e.g. eng, rus, fra, ger, spa, etc.) is also used in translation files (*.lng).  

For example, folder structure for the Hello World module looks as follows.

Structure of Help Folders for the Hello World Module

Accordingly, for each module page the PageNumerator control's parameter will look as follows.

helloworld\html_file_name

or

helloworld\subfolder_name\html_file_name

For more information about creating subtopics, see below.

No doubt that names of files and folders as well as their structure inside the language folder should be completely equal - only contents should differ. For the Hello World module, when you click the Help button in main page header, the "Help" module opens the index.html file in Russian or in English.

Help for the Hello World Module Main Page in English Help for the Hello World Module Main Page in Russian

3.2. Topics and Pages Order

The order of root topics in the "Help" module is based on modules IDs - from smaller to larger. Since custom modules IDs should start from 65536 (65535 - is the ID of the Administration module), help topics for custom modules will be located at the end of the list in the "Help" module's left panel.

Order of Root Help Topics

The internal structure of pages in the help module is defined otherwise. In the source code of HTML pages, in the <head></head> tag you have to add the following meta data:

<meta content="order:n" />

The value of n from smaller to larger determines the order of pages. The least n corresponds to the root topic page.

Order of Help Topics

To create a subtopic, simply create a subfolder like this.

module_name\resources_folder\help\language\folder

Then in this folder, inside HTML documents source code just continue the numbering of n in the <meta content=\"order:n\" /> tag. Remember, that pages of root topic and all subtopics should be named as index.html.

New Help Topics  Page Names for Subfolders

Accordingly, the updated structure of files and folders will look as follows.

Updated Folder Structure   Updated File Structure   Updated Structure of Files in Subfolder

The root topic name is displayed using the IModule::Title method. Names of subtopics and pages are displayed using the <title></title> tag value, specified in HTML source code.

Names of Help Topics

When you edit the names of help topics, the changes will take effect only after server restart.

4. Integration of User Commands in TeamWox Modules

In the last section we will consider how to make custom modules' commands (in form of JavaScript functions) available from other TeamWox modules. JavaScript commands can be run in a separate window (using the Window control), or in the main browser window. 

This task can be solved in two ways.

  1. The IToolbar interface. Using it you can add custom commands to the TeamWox main page header.
  2. The IModuleMainframeTopBar interface. Using it you can load custom scripts in topmost frame for subsequent use in other modules.

Let's consider the implementation of this functionality using the Hello World module as an example (source codes with all the changes are attached to this article).

4.1. User Commands in TeamWox Main Page Header

The IToolbar interface provides several methods that are called by the server, when rendering TeamWox main page header. The server also determines the order of calling these methods.  

  • Total(void) - Returns the total number of custom module commands to be displayed in page header.
  • InfoGet(int toolbar_num, ToolbarInfo *info) - Gets information about commands.  
  • IsAccessible(const Context *context, int toolbar_num) - In this method, you can implement checking permissions for certain commands.
  • ProcessHeader(const Context *context) - Outputs additional code, that loads the JavaScript file with a user-defined function.  

The list of custom module commands, that you want to display in TeamWox main page header, is set in the ToolbarInfo structure.

Field
Type
Description
title
wchar_t[64]
Key of command name translation (text of link).
description
wchar_t[128]
Key of command description translation (tooltip).
url
wchar_t[128]
Command URL.

As an example, we will add a simple JavaScript function, that displays a message box. File with function code should reside in the Hello World module resources (\modules\helloworld\res\js\helloworld.js).   

function HelloWorld_Command()
   {
     alert("<lngj:HELLOWORLD_COMMAND />!!!");
   }

4.1.1. List the IToolbar interface in CHelloWorldModule::GetInterface.  

//---
   if(StringCompareExactly(L"IToolbar",name))
     {
       *iface = &m_toolbar;
       return(RES_S_OK);
     }

4.1.2. For convenience let's implement this interface in the separate class (manager).  

#include "Managers\HelloWorldToolbar.h"
private:
   IServer          *m_server;
   CHelloWorldManager m_manager;
   CHelloWorldToolbar m_toolbar;

4.1.3. Description of the CHelloWorldToolbar class.  

//+------------------------------------------------------------------+
//| Main menu commands                                               |
//+------------------------------------------------------------------+
class CHelloWorldToolbar : public IToolbar
    {
private:
    static ToolbarInfo m_toolbar[];
    //---
    IServer         *m_server;

public:
                    CHelloWorldToolbar();
                   ~CHelloWorldToolbar();
    //---
    TWRESULT        Initialize(IServer *server);
    //---
    int             Total(void);
    TWRESULT        InfoGet(int toolbar_num,ToolbarInfo *info);
    TWRESULT        IsAccessible(const Context *context,int toolbar_num);
    TWRESULT        ProcessHeader(const Context *context);
    };   

In addition to the four methods inherited from the IToolbar interface, declare method of initialization. You can use it to initialize custom commands manager in the main page header during module initialization (CHelloWorldModule::Initialize).  

//---
   if(RES_FAILED(res=m_toolbar.Initialize(m_server)))     ReturnErrorExt(res,NULL,"failed to initialize main page toolbar");

4.1.4. Implementation of the CHelloWorldToolbar class.  

  • Fill out the ToolbarInfo structure by adding command, that calls the HelloWorld_Command() custom function from module resources. Don't forget to add translations for new keys in helloworld.lng language file.
//+------------------------------------------------------------------+
//|                                                          TeamWox |
//|                   Copyright 2006-2011, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net |
//+------------------------------------------------------------------+
#include "StdAfx.h"
#include "HelloWorldToolbar.h"
//--- List of commands
ToolbarInfo CHelloWorldToolbar::m_toolbar[] = {
    {L"HELLOWORLD_COMMAND", L"HELLOWORLD_COMMAND_TITLE", L"javascript:HelloWorld_Command();"}
};
    
  • Implementation of the IToolbar::Total method.
//+------------------------------------------------------------------+
//| Total number of commands                                         |
//+------------------------------------------------------------------+
int CHelloWorldToolbar::Total(void)
   {
//---
    return(_countof(m_toolbar));
   } 
  • Implementation of the IToolbar::Total method.
//+------------------------------------------------------------------+
//| Get info about commands                                          |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldToolbar::InfoGet(int toolbar_num,ToolbarInfo *info)
  {
//--- Checks
   if(toolbar_num<0 || info==NULL) ReturnError(RES_E_INVALID_ARGS);
//---
   if(toolbar_num>=_countof(m_toolbar)) return(RES_E_NOT_FOUND);
//---
       memcpy(info,&m_toolbar[toolbar_num],sizeof(*info));
    //---
       return(RES_S_OK);
      }
    
  • Implementation of the IToolbar::IsAccessible method. For simplicity, we won't implement any permission checking, i.e. Hello World module commands will be available for all TeamWox users.
//+------------------------------------------------------------------+
//| Command accessibility - check permissions                        |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldToolbar::IsAccessible(const Context *context,int toolbar_num)
  {
//--- checks
   if(toolbar_num<0 || context==NULL) ReturnError(RES_E_INVALID_ARGS);
//---
   if(toolbar_num>=_countof(m_toolbar)) return(RES_E_NOT_FOUND);
//---
   return(RES_S_OK);
  }  
  • Implementation of the IToolbar::ProcessHeader method. Output of additional code is performed just like in IModuleMainframeTopBar::MainframeTopBar.
//+------------------------------------------------------------------+
//| Output additional code                                           |
 //+------------------------------------------------------------------+
 TWRESULT CHelloWorldToolbar::ProcessHeader(const Context *context)
   {
//--- Checks
   if(context==NULL || m_server==NULL) ReturnError(RES_E_INVALID_ARGS);
//---
   context->response->Write(L"<script type='text/javascript' src='");
   m_server->WriteStamp(context, L"/helloworld/res/js/helloworld.js");
   context->response->Write(L"'></script>");
//---
   return(RES_S_OK);
   }  
    

The IServer::WriteStamp method appends postfix (hash of last modification date) to the name of resource file. Thus, if the resource file has not changed, it won't be requested when loading the module, as browser can get it from cache. 

If the resource file has changed, it will be explicitly reloaded, as due to changed postfix the resource file will get new name.

4.1.5. So, let's see what we've got. Compile the module, start the server and open TeamWox main page.  

Custom Command in TeamWox main page header    Calling Custom Command from Main Page Header  

4.2. Downloading Module Scripts in Topmost Frame

Sometimes you may need custom commands to be available on any page (for example, tooltip with various information). In such cases it's better to load commands in topmost frame. For this purpose you should use the IModuleMainframeTopBar interface.

The IModuleMainframeTopBar interface provides only one method MainframeTopBar. In implementation of this method you can add your JavaScript code that will be loaded once along with main page code. Then available custom functions can be called from other modules.

As an example to illustrate this concept, we will modify example from the previous topic. Let's put JavaScript function to another file (\modules\helloworld\res\js\helloworld_topbar.js) and change the text of output message.

function HelloWorld_TopCommand()
  {
    alert("Top!!! <lngj:HELLOWORLD_COMMAND />");
  }

4.2.1. In the CHelloWorldModule::GetInterface (const wchar_t *name, void **iface) method list and return the pointer to the IModuleMainframeTopBar interface, that will be implemented in the module.  

//---
   if(StringCompareExactly(L"IModuleMainframeTopBar",name))
     {
       *iface = static_cast<IModuleMainframeTopBar*>(this);
       return(RES_S_OK);
     }

4.2.2. In the module class inherit method of the IModuleMainframeTopBar interface.  

class CHelloWorldModule : public IModule, public IModuleMainframeTopBar

..........................................

public:
      TWRESULT          MainframeTopBar(const Context *context);

4.2.3. In the IModuleMainframeTopBar::MainframeTopBar method implementation, output the HTML code that loads JavaScript file with custom function.

//+------------------------------------------------------------------+
//| Get module's interfaces                                          |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldModule::MainframeTopBar(const Context *context)
      {
//--- Checks
   if(context==NULL || m_server==NULL) ReturnError(RES_E_INVALID_ARGS);
//---
   context->response->Write(L"<script type='text/javascript' src='");
   m_server->WriteStamp(context, L"/helloworld/res/js/helloworld_topbar.js");
   context->response->Write(L"'></script>");
//---
   return(RES_S_OK);
      }

4.2.4. To call a custom function, you must use the following URL. The top prefix means that the function is available on the page, located on a higher level (according to DOM terminology), i.e. in the topmost frame of the page.  

javascript:top.HelloWorld_TopCommand();

Calling Custom Command Loaded in Topmost Frame

Conclusion

We have reviewed the basic aspects of setting custom modules environment in TeamWox groupware. In the second part we will talk about adding custom texts in the "Tips" module. You will also learn about widgets on the main page, as well as distributing custom modules in compressed form.


helloworld-setupenvironment-part1-en.zip (209.33 KB)

2011.03.29