TeamWox SDK: Creating Custom Reports

Introduction

In the Reports in TeamWox and Their Practical Use article we've briefly summarized the nature of reports. This tool allows you to visualize various factors of company's performance based on data from TeamWox modules in the form of various charts.

In addition to standard reports for modules shipped with TeamWox, SDK enables you to create custom reports for any TeamWox module. In this way you can extend TeamWox functionality, customizing it for your needs.

In this article we will examine the reporting tools, provided by TeamWox server API. Then we will move from theory to practice by creating a custom report for the Hello World module.

 

Reports Interfaces

In TeamWox system all reports (both standard and custom) are created using built-in system reports module (TWX_REPORTS). It sets up the basic environment to display reports pages.

To create a custom report your module must implement the IReportsProvider interface, i.e. reports provider. A class that will implement it - is a custom reports provider, that provides reports data to module manager.

Reports providers should be initialized during module initialization phase (IModule::Initialize). On the second phase of module initialization (IModule::PostInitialize) you must get the reports manager interface IReportsManager, and using its IReportsManager::Register method you should register module's reports in the system reports module TWX_REPORTS.

The following 4 methods should be implemented in your reports provider class:

  • PreProcess - Prepare module to process report pages.
  • Process - Redirect HTTP requests to report pages.
  • GetCount - Get number of reports for the module.
  • GetInfo - Report's service information.

When the PreProcess and Process methods are called, one of the parameters passed - is the pointer to the IReportsParams interface, that is used to set up reports parameters.

Reports provider retrieves data and provides results in JSON format. This universal format in addition is used to visualize reports. In UI of page data are visualized using the Chart control from TeamWox library.

 

User Interface

The user interface of reports module consists of two frames and looks as follows.

Reports Module User Interface

The first frame displays the reports module menu that contains the list of available reports. Modules' names are represented as parent nodes, and reports themselves - as their children. For modules from standard TeamWox delivery custom reports are listed first and then come standard reports.

The second frame displays the contents of report page. The report page includes the page header with standard commands, the band with report UI controls (it is set by the IReportsParams::SetFlags method) and the report charts.

 

The Chart Control

In TeamWox all the charts (there are several types of charts) are created using the Chart control. It is created just as well as all other elements of page interface - using the TeamWox.Control() method. Consider the constructor of the Chart control in details.

Chart(chartType, data)

The class constructor accepts two parameters:

  • chartType - The type of chart. There are several types of charts: regular charts, charts with cumulative and normalized values, pie charts, etc. For complete list see the current version of TeamWox SDK documentation, the TeamWox.Chart.Type section (currently in Russian only).
  • data - JSON object containing parameters of selected chart type and data series.

Series - is a sequence of data that are used to create report's chart. There are several types of series that can be applied to certain types of charts. In turn, for a certain type of series a certain set of settings and display types can be applied. For more information, see TeamWox SDK documentation.

Note that it is not necessary to set chart data in the Chart class constructor (i.e. data - is an optional parameter). While working with chart data you'll gain much more flexibility using methods of the Chart class (for complete list see documentation, we will use some of them in our example).

 

Reports Routing Rules

The TeamWox reports system module (TWX_REPORTS) defines the URL format for reports pages' HTTP requests. It must consist of 3 parts:

reports/module_name/report_name

Let's consider them in detail:

1. reports. Redirects HTTP request to process page by system reports module. In this case the reports user interface is loaded. In the module's main page header this part of URL should be included only for the reports pages, since they will be processed by the system reports module TWX_REPORTS.

2. module_name. Module name is set in the structure of the ModuleInfo type, containing information about the module (the name field). Module name is displayed as parent node in the first frame of reports UI along with other modules' names, for which reports are available.

3. report_name. This part of URL specifies the first page that will display report in the second frame of the reports module user interface. You can create multiple reports for one module. Each report must have a corresponding page.

You should put command that opens report page into header of module's main page. For example, the Users report page URL (the Tasks module) looks as follows:

/reports/tasks/users

 

Creating Custom Report for the Hello World Module

In the previous TeamWox SDK: Interaction with DBMS article you have known how the basic interaction with TeamWox DBMS is organized (on example of Hello World training module). We'll continue to work with this module and will add a simple report - distribution of U.S. presidents by their political parties on the simple chart with values on horizontal axis. Here is how it will look like.

Custom Report for Hello World Module

Let's begin.

You can download the finished project as an attachment (see the bottom of this article).

 

1. Adding New Fields to the Table

So we need a numerical dependency. For this we have to add the new field (lets name it party) in the table with U.S. presidents names. This field will store numeric values corresponding to the names of political parties.

1.1. In the \API\HelloWorld.h add the new field in the HelloWorldRecord structure.

//+------------------------------------------------------------------+
//| Record structure                                                 |
//+------------------------------------------------------------------+
struct HelloWorldRecord
  {
   INT64             id;
   wchar_t           name[256];
   int               party;
  };

1.2. Now add this field to DBMS table. To this end, in the CHelloWorldManager::DBTableCheck method supplement the SQL query text.

//+------------------------------------------------------------------+
//| Create HELLOWORLD table                                          |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldManager::DBTableCheck(ISqlBase *sql)
  {
//--- Checks
   if(sql==NULL) ReturnError(RES_E_INVALID_ARGS);
//---
   TWRESULT res=RES_S_OK;
//---
   if(RES_FAILED(res=sql->CheckTable("HELLOWORLD",
       "ID           BIGINT        DEFAULT 0   NOT NULL,"
       "NAME         VARCHAR(256)  DEFAULT ''  NOT NULL,"
       "PARTY        INTEGER       DEFAULT 0   NOT NULL",
       "PRIMARY KEY (ID)",
       "DESCENDING INDEX IDX_HELLOWORLD_ID_DESC (ID)",
                                             NULL,
                                             NULL,
                                              0)))
      ReturnError(res);
//---   
   ExtLogger(NULL,LOG_STATUS_INFO) << "Table 'HELLOWORLD' checked";
//---
   return(RES_S_OK);
  }

1.3. In the C.R.U.D. methods implementations adjust SQL requests and add the new field into structures containing parameters of requests.

  • CHelloWorldManager::InfoGet
//--- Text of SQL request to select records from HELLOWORLD table and sort them by ID. 
   char             query_select[]        ="SELECT id,name,party FROM helloworld ORDER BY id ROWS ? TO ?";
//--- "Bind" data to parameters of request
   SqlParam         params_query_select[] ={
      SQL_INT64, &rec_info_get.id,          sizeof(rec_info_get.id),
      SQL_WTEXT,  rec_info_get.name,        sizeof(rec_info_get.name),
      SQL_LONG,  &rec_info_get.party,       sizeof(rec_info_get.party)
      };
  • CHelloWorldManager::Update
//--- Text of SQL request to add new record in HELLOWORLD table
   char     query_insert[] ="INSERT INTO helloworld(party,name,id) VALUES(?,?,?)";
//--- Text of SQL request to modify existing record in HELLOWORLD table
   char     query_update[] ="UPDATE helloworld SET party=?,name=? WHERE id=?";
//--- "Bind" data to parameters of request
   SqlParam params_query[] ={
      SQL_LONG, &record->party,       sizeof(record->party),
      SQL_WTEXT,  record->name,        sizeof(record->name),
      SQL_INT64, &record->id,          sizeof(record->id)
      };
  • CHelloWorldManager::Get
//--- Text of SQL request to get row from HELLOWORLD table by specified ID. 
   char     query_select_string[]        ="SELECT id,name,party FROM helloworld WHERE id=?";
//--- Parameter of request
   SqlParam params_string[]              ={
      SQL_INT64,&id,sizeof(id)
      };
//--- "Bind" data to parameters of request
   SqlParam params_query_select_string[] ={
      SQL_INT64, &record->id,         sizeof(record->id),
      SQL_WTEXT,  record->name,       sizeof(record->name),
      SQL_LONG, &record->party,      sizeof(record->party)
      };

1.4. Add new field in HTTP API and UI of pages.

  • CPageNumberTwo::JSONDataOutput (Note - the names of parties are represented as keys in module's language file).
TWRESULT CPageNumberTwo::JSONDataOutput(const Context *context,CHelloWorldManager *manager)
  {
   TWRESULT res = RES_S_OK;
   //---
   if(context==NULL || context->response==NULL)
      return(RES_E_INVALID_ARGS);
   //---
   static wchar_t title[][64]={
       L"HELLOWORLD_REPORT_REPUBLICAN",
       L"HELLOWORLD_REPORT_DEMOCRATIC",
       L"HELLOWORLD_REPORT_DEMOREP",
       L"HELLOWORLD_REPORT_FEDERALIST",
       L"HELLOWORLD_REPORT_WHIG",
       L"HELLOWORLD_REPORT_NATUNION", 
       L"HELLOWORLD_REPORT_NOPARTY"
    };
   //---
   m_info_count=_countof(m_info);
   if(RES_FAILED(res=manager->InfoGet(context,m_info,m_start,&m_info_count)))
      ReturnErrorExt(res,context,"failed get info records");
   //---
   CJSON json(context->response);
   json << CJSON::ARRAY;
   for(int i=0;i<m_info_count;i++)
     {
      json << CJSON::OBJECT;
      //---
      json << L"id"          << m_info[i].id;
      json << L"name"        << m_info[i].name;
      //---
      if(m_info[i].party>=0 && m_info[i].party<_countof(title))
         json << L"party" << m_server->GetString(context, NULL, title[m_info[i].party], NULL);
      else
         json << L"party" << m_server->GetString(context, NULL, L"UNKNOWN_PARTY", NULL);
      //---
      json << CJSON::CLOSE;
     }
   //---
   json << CJSON::CLOSE;
   return(RES_S_OK);
  }      
  • CPageEdit::OnUpdate
//--- Filling out the fields
   StringCchCopy(m_record.name, _countof(m_record.name), context->request->GetString(IRequest::POST,L"name"));
   m_record.party=context->request->GetInt32(IRequest::POST,L"party");
  • CPageEdit::Tag
//---
   if(TagCompare(L"party",tag))
     {
      StringCchPrintf(str_party,_countof(str_party),L"",m_record.party);
      context->response->WriteSafe(str_party,IResponse::REPLACE_JAVASCRIPT);
      return(false);
     }
  • number2.tpl
   header: [
               {id:'number',      content:'<lngj:HELLOWORLD_POSITION/>'},
               {id:'name',        content:'<lngj:HELLOWORLD_NAME/>'},
               {id:'party',       content:'<lngj:HELLOWORLD_PARTY/>'}
           ]
      //--- Write data into the records array
      records.push([
            {id:'number',      content:data[i].id},
            {id:'name',        content:data[i].name,toolbar:[['edit',top.Toolbar.Edit],['delete',top.Toolbar.Delete]]},
            {id:'party',       content:data[i].party}
  • edit.tpl. We will set the party by choosing value from the drop-down list. For this we will use the Combobox class - subclass of Input.
items: [
        [
         TeamWox.Control('Label','<lngj:HELLOWORLD_NAME>','name'),
         TeamWox.Control('Input','text','name','<tw:name />')
        ],
        [
         TeamWox.Control('Label','<lngj:HELLOWORLD_PARTY>','party'), 
         TeamWox.Control('Input','combobox','party','<tw:party />', 
                          {
                           options : [
                                       [0,'<lngj:HELLOWORLD_REPORT_REPUBLICAN />'], 
                                       [1,'<lngj:HELLOWORLD_REPORT_DEMOCRATIC />'],
                                       [2,'<lngj:HELLOWORLD_REPORT_DEMOREP />'],
                                       [3,'<lngj:HELLOWORLD_REPORT_FEDERALIST />'],
                                       [4,'<lngj:HELLOWORLD_REPORT_WHIG />'],
                                       [5,'<lngj:HELLOWORLD_REPORT_NATUNION />'],
                                       [6,'<lngj:HELLOWORLD_REPORT_NOPARTY />']
                                     ]
                          })
        ]

 

2. Implementing Interfaces

2.1. For the simplicity, we will implement reports provider interface in module manager's class. So there is nothing to change in module initialization - module's manager initializes in its regular way. But in the module manager's class you have to inherit methods of the IReportsProvider interface.

class CHelloWorldManager : public IReportsProvider

2.2. At the second phase of module initialization (the module is already initialized and you can call other modules' interfaces) you have to register reports manager for Hello World module in system reports module TWX_REPORTS.

//+------------------------------------------------------------------+
//| Post-Initialization                                              |
//| You may call other modules' interfaces                           |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldModule::PostInitialize()
  {
   IReportsManager *reports_mngr=NULL;
   // Get interface of system module TWX_REPORTS
   if(m_server && RES_SUCCEEDED(m_server->GetInterface(TWX_REPORTS, L"IReportsManager", (void**)&reports_mngr))) 
   {
       TWRESULT res = reports_mngr->Register(HELLOWORLD_MODULE_ID, 0, &m_manager);
       if(RES_FAILED(res))
        ReturnErrorExt(res, NULL, "Failed to register");
   }   
   return(RES_S_OK);
  }

2.3. In the manager declare methods of working with reports.

public:
   //---
   //--- Reports provider's methods
   virtual TWRESULT  PreProcess(const Context *context, int module_id, const wchar_t* path, IReportsParams *params);
   virtual TWRESULT  Process(const Context *context, int module_id, const wchar_t* path, IReportsParams *params)   ;
   //---
   virtual TWRESULT  GetCount(const Context *context, int module_id, int *count);
   virtual TWRESULT  GetInfo(const Context *context, int module_id, int index, wchar_t *url, int url_len, 
                             wchar_t *title, int title_len, wchar_t *img_url, int img_url_len, int *group);
   //--- Data for reports
   TWRESULT          ReportUSPresidentsParty2JSON(const Context * context);

2.4. Implement these methods.

  • CHelloWorldManager::PreProcess. Be sure to set the IReportsParams::PARAM_USE_FRAMEWORK flag to load TeamWox library of JavaScript components. Without it your page won't generate reports charts using the Chart control.
//+------------------------------------------------------------------+
//| Prepare to process the report page                               |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldManager::PreProcess(const Context *context, 
                                        int module_id, 
                                        const wchar_t* path, 
                                        IReportsParams *params)
{
//--- проверки
    if(context==NULL || module_id!=HELLOWORLD_MODULE_ID || path==NULL || params==NULL) 
       ReturnError(RES_E_INVALID_ARGS);
    if(context->user==NULL)                                                            
       ReturnError(RES_E_INVALID_CONTEXT);
    if(m_server==NULL)                                                                 
       ReturnError(RES_E_FAIL);
    //--- Process report named as helloworld_report
    if(StringCompareExactly(L"helloworld_report", path))
    {
        //---
        params->SetLiftime(30*60);
        params->SetHelpUrl(L"helloworld/reports");
        params->SetBackUrl(L"/helloworld/index");
        params->SetTitle(m_server->GetString(context, NULL, L"MODULE_TITLE", NULL));
        params->SetFlags(IReportsParams::PARAM_HEADER | IReportsParams::PARAM_BAND | IReportsParams::PARAM_USE_FRAMEWORK);
        //---
        return(RES_S_OK);
    }
    //---
    return(RES_E_NOT_FOUND);
} 
  • CHelloWorldManager::Process. Here we redirect HTTP requests to the report page. Here the helloworld_report - is the third part of the report page's request URL.
//+------------------------------------------------------------------+
//| Process page                                                     |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldManager::Process(const Context *context, 
                                     int module_id, 
                                     const wchar_t* path, 
                                     IReportsParams *params)
{
//--- Checks
    if(context==NULL || module_id!=HELLOWORLD_MODULE_ID || path==NULL || params==NULL) 
      ReturnError(RES_E_INVALID_ARGS);
    if(context->user==NULL)                                                            
      ReturnError(RES_E_INVALID_CONTEXT);
    if(m_server==NULL)                                                                 
      ReturnError(RES_E_FAIL);
//--- 
    if(StringCompareExactly(L"helloworld_report", path))
        return(CPageReportHelloworldReport().Process(context, m_server, path, this, params));
//--- 
    return(RES_S_OK);
}
  • CHelloWorldManager::GetCount. In our example we have only one report. There can be any number of reports, but their actual number depends on access rights of a particular user.
//+------------------------------------------------------------------+
//| Number of report's pages                                         |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldManager::GetCount(const Context *context, int module_id, int *count)
{
//--- Checks
    if(count==NULL || module_id!=HELLOWORLD_MODULE_ID) ReturnError(RES_E_INVALID_ARGS);
//--- 
    *count=1;
//--- 
    return(RES_S_OK);
}
  • CHelloWorldManager::GetInfo.
//+------------------------------------------------------------------+
//| Get info about module's reports                                  |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldManager::GetInfo(const Context *context, int module_id, int index, wchar_t *url, int url_len, 
                                     wchar_t *title, int title_len, wchar_t *img_url, int img_url_len, int *group)
{
//--- Checks
    if(module_id!=HELLOWORLD_MODULE_ID || url==NULL || url_len<=0 || title==NULL || 
       title_len<=0 || img_url==NULL || img_url_len<=0 || group==NULL)
                              ReturnError(RES_E_INVALID_ARGS);
    if(m_server==NULL)        ReturnError(RES_E_FAIL);
    if(context==NULL)         ReturnError(RES_E_INVALID_ARGS);
    if(context->user==NULL)   ReturnError(RES_E_INVALID_CONTEXT);
//--- 
    const wchar_t *transl=NULL;
//--- 
    switch(index)
    {
    case 0: // There is only one report. For each subsequent report add its own 'case'
        transl=m_server->GetString(context, NULL, L"MENU_HELLOWORLD_REPORTS", NULL);
        if(FAILED(StringCchCopyEx(title, title_len, transl, NULL, NULL, STRSAFE_IGNORE_NULLS))) 
          ReturnError(RES_E_FAIL);
        //--- Template filename
        if(FAILED(StringCchCopy(url, url_len, L"helloworld_report")))                           
          ReturnError(RES_E_FAIL);
        //--- Path to report's icon
        if(FAILED(StringCchCopy(img_url, img_url_len, L"/reports/res/i/report.gif")))           
          ReturnError(RES_E_FAIL);
        *group=1;
        break;
    default:
        _ASSERT(0);
    }
//--- 
    return(RES_S_OK);
}
  • CHelloWorldManager::ReportUSPresidentsParty2JSON. This method generates the report data in JSON format. In our case it will be an array of arrays like [Party, Number of presidents in this party].
//+------------------------------------------------------------------+
//| Method that generates JSON array for reports                     |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldManager::ReportUSPresidentsParty2JSON(const Context* context)
{
//--- Checks
   if(context==NULL)                                  ReturnError(RES_E_INVALID_ARGS);
   if(context->response==NULL || context->sql==NULL)  ReturnError(RES_E_INVALID_CONTEXT);
//--- 
    int     party = 0;
    int     count = 0;
    wchar_t title[][64]={
       L"HELLOWORLD_REPORT_REPUBLICAN",
       L"HELLOWORLD_REPORT_DEMOCRATIC",
       L"HELLOWORLD_REPORT_DEMOREP",
       L"HELLOWORLD_REPORT_FEDERALIST",
       L"HELLOWORLD_REPORT_WHIG",
       L"HELLOWORLD_REPORT_NATUNION", 
       L"HELLOWORLD_REPORT_NOPARTY"
    };
//--- Parameters to get the request data
    SqlParam p_out[] = {
       SQL_LONG,  &party,sizeof(party),
       SQL_LONG,  &count,sizeof(count)
    };
//--- Text of request
    char query[] = "SELECT party, COUNT(id) AS cnt FROM HELLOWORLD GROUP BY party";
//--- Make the request
    if(!context->sql->Query(query, NULL, 0, p_out, _countof(p_out)))
        ReturnErrorExt(RES_E_SQL_ERROR, context, "Failed to sql query");
//--- Generate JSON array of arrays like [Party, Number of presidents in this party]
    CJSON json(context->response);
    json << CJSON::ARRAY;
    while(context->sql->QueryFetch())
      {
        json << CJSON::ARRAY;
        //---
        if(party>=0 && party<_countof(title))
           json << m_server->GetString(context, NULL, title[party], NULL);
        else
           json << m_server->GetString(context, NULL, L"UNKNOWN_PARTY", NULL);
        //---
        json << count;
        //---
        json << CJSON::CLOSE;
      }
    json << CJSON::CLOSE;
//--- Release request
    if(!context->sql->QueryFree())
        ReturnErrorExt(RES_E_SQL_ERROR, context, "Failed to sql query free");
//--- 
return(RES_S_OK);    
}

 

3. HTTP API of the Report Page

In the CHelloWorldManager::Process method we've redirected processing of the report page's request to the CPageReportHelloworldReport page. Now we need to implement the class of this page and then create the user interface for it.

Create the report page. For logical organization of reports pages they should be separated from the other module's pages and placed in the Pages\reports\ sub-folder both in project and on the server. The same is true for the templates of reports pages (templates\reports).

  • Declaration. The method of page processing now must also accept the report parameters.
\//+------------------------------------------------------------------+
//|                                                          TeamWox |
//|                 Copyright c 2006-2008, MetaQuotes Software Corp. |
//|                                         https://www.metaquotes.net |
//+------------------------------------------------------------------+
#pragma once
class CHelloWorldManager;
//+------------------------------------------------------------------+
//| Reports page for the HelloWorld module                           |
//+------------------------------------------------------------------+
class CPageReportHelloworldReport : public CPage
   {
private:
    CHelloWorldManager* m_manager;
public:
                        CPageReportHelloworldReport();
                       ~CPageReportHelloworldReport();
    virtual bool        Tag(const Context *context,const TagInfo *tag);
    TWRESULT            Process(const Context *context, 
                                IServer* server, 
                                const wchar_t* path, 
                                CHelloWorldManager* manager, 
                                IReportsParams *params);
   };
  • Implementation. "Wrap" HTTP response with data in JSON format into the data_tag token.
\//+------------------------------------------------------------------+
//|                                                          TeamWox |
//|                 Copyright c 2006-2008, MetaQuotes Software Corp. |
//|                                         https://www.metaquotes.net |
//+------------------------------------------------------------------+
#include "StdAfx.h"
#include "PageReportHelloworldReport.h"
#include "..\..\Managers\HelloWorldManager.h"
//+------------------------------------------------------------------+
//| Constructor                                                     |
//+------------------------------------------------------------------+
CPageReportHelloworldReport::CPageReportHelloworldReport() : m_manager(NULL)
{

}
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CPageReportHelloworldReport::~CPageReportHelloworldReport()
{
    m_manager=NULL;
}
//+------------------------------------------------------------------+
//| Process page                                                    |
//+------------------------------------------------------------------+
TWRESULT CPageReportHelloworldReport::Process(const Context *context, 
                                              IServer* server, 
                                              const wchar_t* path, 
                                              CHelloWorldManager* manager, 
                                              IReportsParams *params)
{
//--- Checks
   if(context==NULL || path==NULL)                   ReturnError(RES_E_INVALID_ARGS);
   if(manager==NULL || server==NULL)                 ReturnError(RES_E_INVALID_ARGS);
   if(context->request==NULL || context->user==NULL) ReturnError(RES_E_INVALID_CONTEXT);

//--- 
    m_manager = manager;
/--- 
    return(server->PageProcess(context, L"templates\\reports\\helloworld_report.tpl", this, TW_PAGEPROCESS_STANDART));
}
//+------------------------------------------------------------------+
//| Process token                                                    |
//+------------------------------------------------------------------+
bool CPageReportHelloworldReport::Tag(const Context *context,const TagInfo *tag)
  {
//--- Checks
   if(context==NULL || tag==NULL)                    return(false);
   if(context->request==NULL || context->user==NULL) return(false);
//--- 
   if(TagCompare(L"data_tag", tag))
     {
      TWRESULT res=m_manager->ReportUSPresidentsParty2JSON(context);
      if(RES_FAILED(res))
         ReturnErrorExt(false, context, "Failed to form hellowrold report");
      return false;
     }
//--- 
   return false;
  }

 

4. User Interface

4.1. Create the user interface for the report page (templates\reports\helloworld_report.tpl). Since the report page is loaded in the separate frame, you don't need to add the basic HTML tags and the code of TeamWox library initialization - all the necessary environment is loaded, if the IReportsParams::PARAM_USE_FRAMEWORK flag has been set.

<script type="text/javascript">

   var chartTitle = '<lngj:REPORT_HELLOWORLD_REPORT_TITLE /> ';
   var page_data  = <tw:data_tag />
   var chart1 = TeamWox.Control('Chart', TeamWox.Chart.Type.BAR,
      {
         title  : chartTitle,
         series :
            [
               {
                  type :   TeamWox.Chart.SeriesType.TITLE
               },
               {
                  type :   TeamWox.Chart.SeriesType.BAR,
                  title:   '<lngj:REPORT_HELLOWORLD_REPORT_SERIES />'
               }
            ]
      }).Style("padding", "5px 4px 5px 4px")
        .SetValues(page_data, true);
</script>

4.2. Add the command to call the report into page header of module's main page. Specify the URL according to the reports routing rule.

//+----------------------------------------------+
//| 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);

4.3. Don't forget to add translations for new keys in helloworld.lng.

[eng]
;---
MENU_HELLOWORLD_REPORTS        ="Reports"
REPORT_HELLOWORLD_REPORT_TITLE ="US Presidents' parties report" 
REPORT_HELLOWORLD_REPORT_SERIES="Presidents per party"
;---
HELLOWORLD_REPORT_REPUBLICAN   ="Republican"
HELLOWORLD_REPORT_DEMOCRATIC   ="Democratic"
HELLOWORLD_REPORT_DEMOREP      ="Democratic-Republican"
HELLOWORLD_REPORT_FEDERALIST   ="Federalist"
HELLOWORLD_REPORT_WHIG         ="Whig"
HELLOWORLD_REPORT_NATUNION     ="National Union"
HELLOWORLD_REPORT_NOPARTY      ="No Party"
;---

[rus]
;---
MENU_HELLOWORLD_REPORTS        ="Отчеты"
REPORT_HELLOWORLD_REPORT_TITLE ="Отчет по партиям президентов США" 
REPORT_HELLOWORLD_REPORT_SERIES="Кол-во президентов на партию"
;---
HELLOWORLD_REPORT_REPUBLICAN   ="Республиканец"
HELLOWORLD_REPORT_DEMOCRATIC   ="Демократ"
HELLOWORLD_REPORT_DEMOREP      ="Демократ-республиканец"
HELLOWORLD_REPORT_FEDERALIST   ="Федералист"
HELLOWORLD_REPORT_WHIG         ="Виг"
HELLOWORLD_REPORT_NATUNION     ="Национальный союз"
HELLOWORLD_REPORT_NOPARTY      ="Беспартийный"

 

5. Custom Report Demonstration

5.1. Compile the module and start the TeamWox server. In Hello World module go to the reports page.

Link to the Reports Page in the Module's Main Page Header

5.2. That opens the user interface of the system reports module. In the first frame we see the module name Hello World, for which we have created report, as well as the name of the report page. The default icon is displayed before the name of report. We have specified the path to this icon in the CHelloWorldManager::GetInfo method.

Hello World Module Report UI

5.3. If you switch to another language of TeamWox interface in your profile (in our case it is Russian), then all texts on the chart will update accordingly.

Report Dynamically Updates

 

Conclusion

From this article you've learned how to create custom reports in TeamWox groupware. As you can see - it's pretty simple. In our example the report has been implemented directly in the module.

If you want to create a report for module from standard TeamWox delivery, then the implementation approach in this case is slightly differs. Third-party developers don't have access to standard modules' source codes, so the solution here is to create a separate module that implements necessary reports.

An example of such a module called ReportsExt is included in TeamWox SDK. This module implements custom reports for the Chat module (evaluation of the quality of employees' work and quality of clients servicing based on communication between customers with employees in chat) and the Service Desk (users activity of posting requests sorted by category).

In our next article we are going to highlight the aspects of using TeamWox file storage.


helloworld-custom-reports-en.zip (164.26 KB)

2011.01.07