TeamWox SDK: Создание пользовательских отчетов

Введение

В статье Отчеты в TeamWox и их практическая польза кратко изложена сущность отчетов. Это средство позволяет в наглядном виде представить показатели работы организации на основе данных из модулей TeamWox в виде различных графиков и диаграмм.

Помимо стандартных отчетов для модулей, входящих в поставку TeamWox, инструментарий SDK дает возможность создавать пользовательские отчеты для любого модуля TeamWox. Так вы сможете расширить функциональность системы TeamWox, адаптируя ее под собственные нужды.

В этой статье мы рассмотрим какие же средства создания отчетов предоставляет API сервера TeamWox, а затем закрепим теорию практикой, создав собственный отчет для модуля Hello World.

 

Интерфейсы создания отчетов

В системе TeamWox все отчеты (стандартные и пользовательские) создаются средствами системного модуля отчетов (TWX_REPORTS). Он задает базовое окружение для отображения страниц отчетов.

Для создания пользовательских отчетов модуль должен реализовать интерфейс IReportsProvider - провайдеров отчетов. Класс, который будет его реализовывать, представляет собой провайдер клиентских отчетов, предоставляющий данные отчетов менеджеру модуля.

Провайдеры отчетов следует инициализировать на стадии инициализации модуля (IModule::Initialize). На втором этапе инициализации модуля (IModule::PostInitialize) необходимо получить интерфейс менеджера отчетов IReportsManager и его методом IReportsManager::Register зарегистрировать отчеты модуля в системном модуле отчетов TWX_REPORTS.

Следующие 4 метода должны быть реализованы в вашем классе провайдера отчетов:

  • PreProcess - подготовка модуля к обработке страниц отчета.
  • Process - перенаправление запросов на страницы отчета.
  • GetCount - определение количества отчетов для модуля.
  • GetInfo - служебная информация об отчете.

При вызове методов PreProcess и Process одним из параметров передается указатель на интерфейс IReportsParams, который используется для настройки параметров отчетов.

Провайдер отчетов делает выборку данных и предоставляет результаты в формате JSON. Этот универсальный формат в том числе используется и для визуализации отчетов. В пользовательском интерфейсе страницы за визуализацию данных отвечает элемент управления Chart из библиотеки TeamWox.

 

Пользовательский интерфейс

Пользовательский интерфейс модуля отчетов состоит из двух фреймов и выглядит следующим образом.

Пользовательский интерфейс модуля отчетов

В первом фрейме отображается меню модуля отчетов, содержащее список доступных отчетов. Имена модулей представляются в виде категорий, а отчеты - в виде их дочерних элементов. Для модулей из стандартной поставки TeamWox в списке сначала перечисляются пользовательские отчеты, а затем - стандартные отчеты.

Во втором фрейме отображается содержание страницы отчета. Страница отчета содержит шапку со стандартными командами, полоску с элементами управления отчетом (задаются методом IReportsParams::SetFlags) и собственно графики отчетов.

 

Элемент управления Chart

В системе TeamWox все графики (их существует несколько типов) создаются с помощью элемента управления Chart. Создается он точно также, как и другие элементы интерфейса страницы - методом TeamWox.Control(). Рассмотрим конструктор элемента управления Chart поподробнее.

Chart(chartType, data)

В конструкторе класса задаются два параметра:

  • chartType - тип графика. Существует несколько типов графиков: обычные графики, графики с накопительными и нормализованными значениями, графики в виде круговой диаграммы и др. Ознакомиться с полным перечнем можно в текущей версии документации по TeamWox SDK в разделе TeamWox.Chart.Type.
  • data - JSON объект, содержащий параметры выбранного типа графика и серию данных.

Серия - это последовательность данных, по которым строится график отчета. Существует несколько типов серий, которые применимы к определенным типам графиков. В свою очередь, к каждому типу серии применим свой определенный набор настроек параметров и типов отображения. Подробнее см. документацию по TeamWox SDK.

Обратите внимание, что данные для графика не обязательно задавать в конструкторе класса Chart (т.е. data - необязательный параметр). Куда большую гибкость при работе с данными графика дают методы класса Chart (с ними также можно ознакомиться в документации, некоторые из них мы используем в нашем примере).

 

Правила маршрутизации отчетов

Системный модуль отчетов TeamWox (TWX_REPORTS) определяет формат URL для запросов страниц отчетов. Он должен состоять из 3-х частей:

reports/имя_модуля/имя_отчета

Рассмотрим их подробнее:

1. reports. Перенаправляет запрос на обработку страницы системным модулем отчетов. При этом загружается пользовательский интерфейс отчетов. В шапке главной страницы модуля эту часть URL следует указывать только для страниц отчетов, поскольку они будут обрабатываться системным модулем отчетов TWX_REPORTS.

2. имя_модуля. Задается в структуре типа ModuleInfo, содержащей информацию о модуле (поле name). Имя модуля отображается как родительский элемент в первом фрейме интерфейса отчетов наряду с именами других модулей, для которых доступны отчеты.

3. имя_отчета. Эта часть пути определяет первую страницу, которая будет отображать отчет втором фрейме интерфейса модуля отчетов. Для модуля можно создать несколько отчетов, каждому из которых будет соответствовать отдельная страница.

Команда вызова страницы отчета должна находиться в шапке главной страницы модуля. Например, URL страницы отчета "Пользователи" модуля "Задания" выглядит следующим образом:

/reports/tasks/users

 

Создание пользовательского отчета для модуля Hello World

В предыдущей статье TeamWox SDK: Взаимодействие с СУБД вы узнали, как организуется базовое взаимодействие с СУБД TeamWox на примере учебного модуля Hello World. Продолжая работать с этим модулем, мы добавим в него простейший отчет - распределение президентов США по их политическим партиям на графике со значениями по горизонтальной оси X. Вот так он будет выглядеть.

Пользовательский отчет для модуля Hello World

Приступим.

Готовый проект со всеми изменениями доступен в виде приложения к статье (см. прикрепленный файл).

 

1. Добавление новых полей в таблицу

Чтобы у нас проявилась нужна числовая зависимость, в таблицу с именами президентов США мы добавим новое поле party. В этом поле будут храниться числовые значения, соответствующие названиям политических партий.

1.1. В \API\HelloWorld.h добавьте новое поле в структуре HelloWorldRecord.

//+------------------------------------------------------------------+
//| Структура записи                                                 |
//+------------------------------------------------------------------+
struct HelloWorldRecord
  {
   INT64             id;
   wchar_t           name[256];
   int               party;
  };

1.2. Теперь добавьте это поле в таблицу СУБД. Для этого в методе CHelloWorldManager::DBTableCheck соответственным образом дополните текст SQL-запроса.

//+------------------------------------------------------------------+
//| Создание таблицы HELLOWORLD                                      |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldManager::DBTableCheck(ISqlBase *sql)
  {
//--- проверки
   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. В реализации методов C.R.U.D. скорректируйте тексты SQL-запросов, а также добавьте новое поле в структуры, содержащие параметры запросов.

  • CHelloWorldManager::InfoGet
//--- текст SQL-запроса на выборку записей из таблицы HELLOWORLD с сортировкой по полю ID. 
   char             query_select[]        ="SELECT id,name,party FROM helloworld ORDER BY id ROWS ? TO ?";
//--- "Привязываем" данные к параметрам запроса
   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
//--- текст SQL-запроса на добавление новой записи в таблицу HELLOWORLD
   char     query_insert[] ="INSERT INTO helloworld(party,name,id) VALUES(?,?,?)";
//--- текст SQL-запроса на изменение существующей записи в таблице HELLOWORLD
   char     query_update[] ="UPDATE helloworld SET party=?,name=? WHERE id=?";
//--- "Привязываем" данные к параметрам запроса
   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
//--- текст SQL-запроса на получение строки из таблицы HELLOWORLD по указанному ID. 
   char     query_select_string[]        ="SELECT id,name,party FROM helloworld WHERE id=?";
//--- Параметр запроса
   SqlParam params_string[]              ={
      SQL_INT64,&id,sizeof(id)
      };
//--- "Привязываем" данные к параметрам запроса
   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. Добавьте новое поле в HTTP API и пользовательский интерфейс страниц.

  • CPageNumberTwo::JSONDataOutput (обратите внимание - названия партий представляются в виде ключей в языковом файле модуля).
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
//--- заполняем поля
   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/>'}
           ]
      //--- Записываем данные в массив records
      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. Название партии мы будем задавать, выбирая значения из выпадающего списка. Для этого используется подтип класса Input - класс Combobox.
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. Реализация интерфейсов

2.1. Для простоты, интерфейс провайдера отчетов мы будем реализовывать в классе менеджера модуля, поэтому на этапе инициализации модуля ничего не меняется - все также инициализируется менеджер модуля. Но в самом классе менеджера модуля необходимо унаследовать методы интерфейса IReportsProvider.

class CHelloWorldManager : public IReportsProvider

2.2. На втором этапе инициализации (когда сам модуль инициализирован и можно запрашивать интерфейсы других модулей) нам потребуется зарегистрировать менеджер отчетов модуля Hello World в системном модуле отчетов TWX_REPORTS.

//+------------------------------------------------------------------+
//| Пост-Инициализация                                               |
//|  можно запрашивать интерфейсы других модулей                     |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldModule::PostInitialize()
  {
   IReportsManager *reports_mngr=NULL;
   // получим интерфейс системного модуля 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. В менеджере объявите методы работы с отчетами.

public:
   //---
   //--- Методы провайдера отчетов
   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);
   //--- Данные для отчетов
   TWRESULT          ReportUSPresidentsParty2JSON(const Context * context);

2.4. Реализуйте эти методы.

  • CHelloWorldManager::PreProcess. Обязательно устанавливайте флаг IReportsParams::PARAM_USE_FRAMEWORK для загрузки библиотеки JavaScript-компонентов TeamWox. Без него страница не сможет генерировать графики отчетов с помощью элемента управления Chart.
//+------------------------------------------------------------------+
//| Подготовка к обработке страницы отчета                           |
//+------------------------------------------------------------------+
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);
    //--- Обработка отчета с названием 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. Здесь мы перенаправляем запросы на страницу отчета. Здесь helloworld_report - это третья часть URL запроса страницы отчета.
//+------------------------------------------------------------------+
//| Обработка страницы                                               |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldManager::Process(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);
//--- 
    if(StringCompareExactly(L"helloworld_report", path))
        return(CPageReportHelloworldReport().Process(context, m_server, path, this, params));
//--- 
    return(RES_S_OK);
}
  • CHelloWorldManager::GetCount. В нашем примере отчет один. Отчетов может быть больше, а их количество может варьироваться в зависимости от прав доступа конкретного пользователя.
//+------------------------------------------------------------------+
//| Количество страниц отчета                                        |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldManager::GetCount(const Context *context, int module_id, int *count)
{
//--- Проверки
    if(count==NULL || module_id!=HELLOWORLD_MODULE_ID) ReturnError(RES_E_INVALID_ARGS);
//--- 
    *count=1;
//--- 
    return(RES_S_OK);
}
  • CHelloWorldManager::GetInfo.
//+------------------------------------------------------------------+
//| Получение информации об отчетах модуля                           |
//+------------------------------------------------------------------+
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)
{
//--- Проверки
    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: // отчет всего один, для каждого последующего добавляем свой 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);
        //--- Имя файла шаблона
        if(FAILED(StringCchCopy(url, url_len, L"helloworld_report")))                           
          ReturnError(RES_E_FAIL);
        //--- Путь к иконке для отчета
        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. Этот метод формирует данные для отчета в формате JSON. В нашем случае это будет массив массивов вида [партия, кол-во президентов в этой партии].
//+------------------------------------------------------------------+
//| Метод, формирующий JSON-массив для отчета                        |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldManager::ReportUSPresidentsParty2JSON(const Context* context)
{
//--- Проверки
   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"
    };
//--- параметры для получения данных запроса
    SqlParam p_out[] = {
       SQL_LONG,  &party,sizeof(party),
       SQL_LONG,  &count,sizeof(count)
    };
//--- Текст запроса
    char query[] = "SELECT party, COUNT(id) AS cnt FROM HELLOWORLD GROUP BY party";
//--- Выполним запрос
    if(!context->sql->Query(query, NULL, 0, p_out, _countof(p_out)))
        ReturnErrorExt(RES_E_SQL_ERROR, context, "Failed to sql query");
//--- Формируем JSON-массив массивов вида [партия,кол-во президентов в этой партии]
    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;
//--- Освободим запрос
    if(!context->sql->QueryFree())
        ReturnErrorExt(RES_E_SQL_ERROR, context, "Failed to sql query free");
//--- 
return(RES_S_OK);    
}

 

3. HTTP API страницы отчета

В методе CHelloWorldManager::Process мы перенаправили обработку запроса страницы отчета на страницу CPageReportHelloworldReport. Теперь нам нужно реализовать класс этой страницы, а затем создать для нее пользовательский интерфейс.

Создайте страницу отчета. Для логической организации страниц отчетов их следует отделять от остальных страниц модуля и помещать в подпапку Pages\reports в проекте и на сервере. То же самое касается и шаблонов страниц отчетов (templates\reports).

  • Описание. Метод обработки страницы теперь должен принимать параметры отчета.
//+------------------------------------------------------------------+
//|                                                          TeamWox |
//|                 Copyright c 2006-2008, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net |
//+------------------------------------------------------------------+
#pragma once
class CHelloWorldManager;
//+------------------------------------------------------------------+
//| Страница отчетов для модуля HelloWorld                           |
//+------------------------------------------------------------------+
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);
   };
  • Реализация. HTTP-ответ с данными в формате JSON "заворачиваем" в токен data_tag.
//+------------------------------------------------------------------+
//|                                                          TeamWox |
//|                 Copyright c 2006-2008, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net |
//+------------------------------------------------------------------+
#include "StdAfx.h"
#include "PageReportHelloworldReport.h"
#include "..\..\Managers\HelloWorldManager.h"
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CPageReportHelloworldReport::CPageReportHelloworldReport() : m_manager(NULL)
{

}
//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CPageReportHelloworldReport::~CPageReportHelloworldReport()
{
    m_manager=NULL;
}
//+------------------------------------------------------------------+
//| Обработка страницы                                               |
//+------------------------------------------------------------------+
TWRESULT CPageReportHelloworldReport::Process(const Context *context, 
                                              IServer* server, 
                                              const wchar_t* path, 
                                              CHelloWorldManager* manager, 
                                              IReportsParams *params)
{
//--- Проверки
   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));
}
//+------------------------------------------------------------------+
//| Обработка токена                                                 |
//+------------------------------------------------------------------+
bool CPageReportHelloworldReport::Tag(const Context *context,const TagInfo *tag)
  {
//--- Проверки
   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. Пользовательский интерфейс

4.1. Создайте пользовательский интерфейс страницы отчета (файл templates\reports\helloworld_report.tpl). Поскольку страница отчета загружается в отдельном фрейме пользовательского интерфейса модуля отчетов, то базовые HTML-тэги и код инициализации библиотеки TeamWox добавлять не нужно - все необходимое окружение загружается, если установлен флаг IReportsParams::PARAM_USE_FRAMEWORK.

<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. Добавьте команду вызова страницы отчета в шапку главной страницы модуля. URL указывается согласно правилу маршрутизации запросов.

//+----------------------------------------------+
//| Шапка страницы с командами                   |
//+----------------------------------------------+
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. Не забудьте добавить переводы для новых ключей в файл 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. Демонстрация пользовательского отчета

5.1. Скомпилируйте модуль и запустите сервер. В модуле Hello World перейдите на страницу отчетов.

Ссылка на страницу отчета в шапке главной страницы модуля

5.2. Открывается интерфейс системного модуля отчетов. В первом фрейме мы видим имя модуля Hello World, для которого мы создали отчет, а также имя страницы отчета. Перед именем отображается стандартная иконка, которую мы указали в методе CHelloWorldManager::GetInfo.

Интерфейс отчета для модуля Hello World

5.3. Если переключиться на русский язык интерфейса, то тексты графика соответственным образом изменятся.

Динамическое обновление отчета

 

Заключение

В этой статье вы узнали, как в TeamWox создаются пользовательские отчеты. Как видите - это довольно просто. В нашем примере реализация отчета выполняется непосредственно в самом модуле.

Если же вы захотите создать отчет для модуля из стандартной поставки TeamWox, то подход к реализации в этом случае немного меняется. Исходные коды стандартных модулей не могут быть открыты для разработчиков модулей, поэтому решением здесь будет создание отдельного модуля, реализующего необходимые отчеты.

Пример такого модуля под названием ReportsExt включен в TeamWox SDK. В этом модуле реализованы пользовательские отчеты для модулей Чат (оценка качества работы сотрудников и качества обслуживания организаций на основе данных общения сотрудников с клиентами в чате) и Сервисдеск (активность пользователей при выставлении ими заявок по категориям).

В следующей статье планируется осветить вопросы использования хранилища файлов в TeamWox.


helloworld-custom-reports-ru.zip (166.4 KB)

2010.12.28