TeamWox SDK: Настройка окружения пользовательских модулей - Часть 2

Введение

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

1. Менеджер кэширования советов
    1.1. Интерфейсы
    1.2. Пример расширения списка советов
2. Виджеты
    2.1. Интерфейс IWidgets
    2.2. Пример реализации пользовательского виджета
3. Утилита ResPacker

 

1. Менеджер кэширования советов

Модуль "Home" (первая вкладка по-умолчанию среди модулей из стандартной поставки) отображает различную информацию в виде коротких однострочных сообщений внизу главной страницы TeamWox. Это может быть как информация в виде советов по работе в системе TeamWox,

Советы по работе в системе TeamWox

так и информация о деятельности компании например, дни рождения сотрудников.

Ближайшие дни рождения сотрудников

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

 

1.1. Интерфейсы

Для работы менеджером кэша советов, пользовательский модуль должен предоставлять интерфейс IModuleTips, описанный в TeamWox API.

IModuleTips

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

Как и другие интерфейсы, расширяющие функциональность модуля, интерфейс IModuleTips следует перечислить и вернуть в реализации метода IModule::GetInterface для вашего модуля. Системный модуль "Home" будет запрашивать этот интерфейс при работе с модулем.

Рассмотрим требования к реализации методов интерфейса IModuleTips.

IModuleTips::GetTipsCount. Должен возвращать общее количество советов для модуля.

virtual int  GetTipsCount(const Context *context)

Параметр Тип Описание
context Context* Контекст обработки запроса.

IModuleTips::GetTip. Должен возвращать указатель на статическую (неизменяемую) строку с текстом совета из общего списка строк по указанному индексу.

virtual const wchar_t*  GetTip(const Context *context, int index)
Параметр Тип Описание
context Context* Контекст обработки запроса.
index int Индекс совета.

IModuleTips::GetTipEx. Должен возвращать указатель на строку с динамически изменяющимся содержимым. Например, дни рождения сотрудников (модуль Сотрудники), статьи (модуль Управление) и т.п.

virtual TWRESULT  GetTipEx(const Context *context,int index,wchar_t *buf,size_t len)
Параметр Тип Описание
context Context* Контекст обработки запроса.
index int Индекс строки.
buf wchar_t* Буфер с текстом.
len size_t Размер буфера с текстом.

Для модуля можно отображать либо только статические, либо только динамически строки, т.е. результаты работы методов IModuleTips::GetTip и IModuleTips::GetTipEx являются взаимоисключающими.

В модуле "Home" приоритет вывода имеет информация о ближайших днях рождения сотрудников. Эта логика заложена в сам сервер TeamWox, и изменить такое поведение средствами API нельзя.

 

ITipsManager

Менеджер кэширования советов доступен в виде интерфейса ITipsManager. Список советов модуля должен хранится в текстовом файле в ресурсах модуля. Такой формат хранения позволяет редактировать и обновлять советы не только разработчикам, но также техническим писателям и переводчикам. Указатель на интерфейс ITipsManager следует запрашивать в классе модуля, который реализует интерфейс IModuleTips.

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

Формат текстового файла (в кодировке UTF-16) с советами аналогичен формату языкового файла с переводами пользовательского интерфейса. Сначала задается языковая группа в виде трехбуквенного кода в квадратных скобках, а затем идут строки с советами. И так для каждого языка, например.

[eng]
строка 1
строка 2
........
[rus]
строка 1
строка 2
........

При этом строки могут содержать как обычный текст, так и гиперссылки. Например, ссылка на раздел справки (см. первую часть статьи) добавляется следующим образом:

<a href="#" onclick="top.TW_Help('имя_модуля/раздел/имя_hmtl_файла');return(false);">текст ссылки</a> 

Рассмотрим описание методов интерфейса ITipsManager.

ITipsManager::Register. Регистрирует внешний текстовый файл с советами.

virtual TWRESULT  Register(int module_id, const wchar_t *subpath)
Параметр Тип Описание
module_id int Идентификатор модуля.
subpath wchar_t* Путь к файлу с советами относительно DLL модуля.

ITipsManager::GetTipsCount. Возвращает количество советов, хранящихся в кэше для указанного модуля.

virtual int  GetTipsCount(const Context *context, int module_id)
Параметр Тип Описание
context Context* Контекст обработки запроса.
module_id int Идентификатор модуля.

ITipsManager::GetTip. Возвращает строку с текстом совета из кэша по индексу.

virtual const wchar_t*  GetTip(const Context *context, int module_id, int index)
Параметр Тип Описание
context Context* Контекст обработки запроса.
module_id int Идентификатор модуля.
index int Индекс строки.

Файл с советами удобнее всего размещать в папке с шаблонами модуля, либо в корневой папке модуля.

 

1.2. Пример расширения списка советов

Рассмотрим реализацию описанных выше возможностей на примере модуля Hello World. Исходные коды со всеми изменениями доступны в прикрепленном к статье файле.

1. Расширьте функционал модуля, унаследовав методы интерфейса IModuleTips.

class CHelloWorldModule : public IModule, public IModuleMainframeTopBar, public IModuleTips

В нашем примере мы будем реализовывать отображение статических советов, поэтому в объявлении метода GetTipEx сразу же вернем соответствующий код.

//--- подсказки
int            GetTipsCount(const Context *context);
const wchar_t* GetTip(const Context *context, int index);
TWRESULT       GetTipEx(const Context* /*context*/,int /*index*/,wchar_t* /*buf*/,size_t /*len*/) { return(RES_E_NOT_IMPLEMENTED); }

2. Перечислите и верните интерфейс IModuleTips в списке реализуемых интерфейсов в CHelloWorldModule::GetInterface.

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

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

private:
   IServer           *m_server;
   ITipsManager      *m_tips_manager;
   CHelloWorldManager m_manager;
   CHelloWorldToolbar m_toolbar;

4. Проинициализируйте менеджер советов.

//--- готовим подсказки
   if(RES_FAILED(res=m_server->GetInterface(L"ITipsManager",(void**)&m_tips_manager)))
      ExtLogger(NULL,LOG_STATUS_ERROR) << "failed to get ITipsManager";

5. Зарегистрируйте текстовый файл с советами на этапе инициализации модуля.

//---
   if(m_tips_manager!=NULL)
      m_tips_manager->Register(HELLOWORLD_MODULE_ID,L"templates\\tips.txt");

6. Добавьте в текстовый файл несколько советов.

[eng]
HelloWorld tips
HelloWorld tips2
[rus]
Совет HelloWorld2
Совет HelloWorld

7. Реализуйте методы IModuleTips::GetTipsCount и IModuleTips::GetTip.

  • CHelloWorldModule::GetTipsCount
//+------------------------------------------------------------------+
//| Получение количества подсказок                                   |
//+------------------------------------------------------------------+
int CHelloWorldModule::GetTipsCount(const Context *context)
  {
   if(m_tips_manager==NULL) ReturnError(0);
//--- прокидываем
   return(m_tips_manager->GetTipsCount(context, HELLOWORLD_MODULE_ID));
  }
  • CHelloWorldModule::GetTip
//+------------------------------------------------------------------+
//| Получение подсказок                                              |
//+------------------------------------------------------------------+
const wchar_t* CHelloWorldModule::GetTip(const Context *context, int index)
  {
   if(m_tips_manager==NULL) ReturnError(NULL);
//--- прокидываем
   return(m_tips_manager->GetTip(context, HELLOWORLD_MODULE_ID, index));
  }

8. Скомпилируйте проект и убедитесь, что на сервере в папке modules\helloworld\templates находится файл tips.txt с советами. Если совет для модуля Hello World не отображается сразу, прокрутите ленту с советами, нажимая кнопку Кнопка показа следующего совета до тех пор, пока не появится нужный совет.

Советы для модуля Hello World

 

2. Виджеты

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

Виджеты модулей из стандартной поставки TeamWox

Пользовательский интерфейс модуля "Home" дает возможность регулировать размеры и местоположение виджетов на главной странице.

Изменение размеров виджета      Изменение расположения виджета

Средствами TeamWox SDK вы можете создавать виджеты для пользовательских модулей, расширяя тем самым их функциональность.

По своей сути, виджеты - это представление данных (согласно концепции архитектуры MVC), т.е. формируются по тем же принципам, что и обычные страницы модулей, за небольшим исключением. Код шаблона виджета содержит непосредственно содержимое для отображения, без базовых HTML-тэгов <html></html>, <head></head> и <body></body>.

 

2.1. Интерфейс IWidgets

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

Рассмотрим требования к реализации методов интерфейса IWidgets.

IWidgets::Total. Должен возвращать общее количество виджетов.

virtual int Total(void)

IWidgets::InfoGet. Должен получать информацию о конкретном виджете.

virtual TWRESULT  InfoGet(int widget_num,WidgetInfo *info) 
Параметр Тип Описание
widget_num int Индекс виджета.
info WidgetInfo* Информация о виджете. Описание структуры WidgetInfo см. ниже.

IWidgets::IsAccessible. Должен проверить права доступа к виджету.

virtual TWRESULT  IsAccessible(const Context *context,const wchar_t *guid)
Параметр Тип Описание
context Context* Контекст обработки запроса.
guid wchar_t* Текстовый идентификатор виджета.

IWidgets::ProcessHeader. Должен выводить код заголовка, который загружает окружение виджета. В шаблоне заголовка можно добавить код загрузки пользовательских скриптов.

virtual TWRESULT  ProcessHeader(const Context *context,const wchar_t *widget_guid)    
Параметр Тип Описание
context Context* Контекст обработки запроса.
widget_guid wchar_t* Текстовый идентификатор виджета.

IWidgets::ProcessContent. Должен выводить содержимое виджета. В зависимости от передаваемых ширины и высоты виджета, определяется количество отображаемого содержимого.

virtual TWRESULT  ProcessContent(const Context *context,const wchar_t *widget_guid, int width, int height)
Параметр Тип Описание
context Context* Контекст обработки запроса.
widget_guid wchar_t* Текстовый идентификатор виджета.
width int Условная ширина области виджета.
height int Условная высота области виджета.

Структура WidgetInfo

Поле Тип Описание
type int Тип виджета. Задается константами из соответствующего анонимного перечисления, включенного в структуру (см. далее).
object_guid wchar_t[32] Уникальный текстовый идентификатор виджета. Может быть как случайной последовательностью символов, так и представлять собой осмысленное название. Рекомендуется следующая схема наименования: <имя_модуля>_widget_<краткое_название_виджета>. Например: helloworld_widget_simplewidget.
title wchar_t[64] Идентификатор перевода для заголовка виджета.
title_url wchar_t[128]

URL, по которому запрашивается необходимая страница модуля. Текстом ссылки при этом является поле title.

Например, может быть два виджета, первый из которых отображает главную страницу модуля, а второй - конкретный раздел (как это реализовано в виджетах Последние задания и Избранные задания).

description wchar_t[128] Идентификатор перевода для описания виджета. Описание виджета отображается в режиме разметки виджетов на главной странице, а также при наведении курсора мыши на иконку виджета.
width_size int Условная ширина области виджета. Задается константами из соответствующего анонимного перечисления, включенного в структуру (см. далее).
height_size int Условная высота области виджета. Задается константами из соответствующего анонимного перечисления, включенного в структуру (см. далее).
icon_url wchar_t[64] Путь к иконке виджета. Указывается относительно папки \modules на сервере TeamWox.
Для виджетов применяются относительные размеры, которые адаптируются под различные разрешения экрана.

Анонимное перечисление типов виджетов

Имя Значение Описание
TYPE_STATIC 1 Статический виджет. Для статических виджетов запрашиваются два файла - заголовок виджета с необходимым окружением, и содержимое виджета.
TYPE_DYNAMIC 2 Динамический виджет. В таком случае запрашивается только заголовок виджета, в котором с помощью JavaScript-функций содержимое виджета формируется при его загрузке.
TYPE_ONLYDESKTOP 0x80 Флаг, который устанавливает режим отображения виджета только в десктопных браузерах. В браузерах мобильных устройств виджет отображаться не будет.

Анонимное перечисление размеров сторон виджетов

Имя Значение Описание
SIDE_TINY 1 Наименьший размер высоты или ширины виджета.
SIDE_SMALL 2 Малый размер высоты или ширины виджета.
SIDE_MEDIUM 3 Средний размер высоты или ширины виджета.
SIDE_LARGE 4 Наибольший размер высоты или ширины виджета.

 

2.2. Пример реализации пользовательского виджета

В качестве примера мы реализуем виджет, который будет отображать данные со страницы CPageNumberTwo модуля Hello World. Исходные коды со всеми изменениями доступны в прикрепленном к статье файле. За основу реализации взяты исходные коды модуля Форум, доступные в TeamWox SDK.

 

2.2.1. Реализация интерфейса IWidgets

1. Перечислите и верните IWidgets в списке реализуемых интерфейсов в методе CHelloWorldModule::GetInterface.

//---
   if(StringCompareExactly(L"IToolbar",name))               { *iface=&m_toolbar;                                 return(RES_S_OK); }
   if(StringCompareExactly(L"IModuleMainframeTopBar",name)) { *iface=static_cast<IModuleMainframeTopBar*>(this); return(RES_S_OK); }
   if(StringCompareExactly(L"IModuleTips", name))           { *iface=static_cast<IModuleTips*>(this);            return(RES_S_OK); }
   if(StringCompareExactly(L"IWidgets",name))               { *iface=&m_widgets;                                 return(RES_S_OK); }
//---

2. Создайте новый менеджер виджетов в отдельном классе. В нем мы унаследуем методы интерфейса IWidgets, а также добавим метод инициализации, который вызовем на этапе инициализации модуля.

//+------------------------------------------------------------------+
//| Виджет                                                           |
//+------------------------------------------------------------------+
class CHelloWorldWidgets: public IWidgets
  {
private:
   IServer            *m_server;
   CHelloWorldManager *m_manager;

public:
                     CHelloWorldWidgets();
                    ~CHelloWorldWidgets();
   //---
   TWRESULT          Initialize(IServer *m_server,CHelloWorldManager *manager);
   //---
   int               Total(void);
   TWRESULT          InfoGet(int widget_num,WidgetInfo *info);
   TWRESULT          IsAccessible(const Context *context,const wchar_t *guid);
   TWRESULT          ProcessHeader(const Context *context,const wchar_t *widget_guid);
   TWRESULT          ProcessContent(const Context *context,const wchar_t *widget_guid, int width, int height);
  };

3. Проинициализируйте менеджер виджетов в модуле.

  • class CHelloWorldModule - объявление в классе модуля.
private:
   IServer          *m_server;
   ITipsManager     *m_tips_manager;
   CHelloWorldManager m_manager;
   CHelloWorldToolbar m_toolbar;
   CHelloWorldWidgets m_widgets;
  • CHelloWorldModule::Initialize - вызов на этапе инициализации модуля.
if(RES_FAILED(res=m_widgets.Initialize(m_server,&m_manager)))  ReturnErrorExt(res,NULL,"failed to initialize widgets manager");
  • CHelloWorldModule::Initialize - реализация в классе менеджера виджетов.
//+------------------------------------------------------------------+
//| Инициализация                                                    |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldWidgets::Initialize(IServer *server,CHelloWorldManager *manager)
  {
//--- проверки
   if(server==NULL || manager==NULL) return(RES_E_INVALID_ARGS);
//---
   m_server =server;
   m_manager=manager;
//--- все ок
   return(RES_S_OK);
  }

4. В реализации менеджера виджетов заполните структуру WidgetInfo.

//---
static WidgetInfo widgets[]=
  {
       {
         WidgetInfo::TYPE_STATIC,                   // Тип виджета
         L"helloworld_widget_pagetwo",              // Текстовый идентификатор виджета
         L"HELLOWOLRD_WIDGET_SIMPLE_TITLE",         // Заголовок области виджета
         L"/helloworld/index",                      // Ссылка в заголовке виджета
         L"HELLOWORLD_WIDGET_SIMPLE_DESCRIPTION",   // Описание виджета
         WidgetInfo::SIDE_MEDIUM,                   // Ширина по-умолчанию
         WidgetInfo::SIDE_MEDIUM,                   // Высота по-умолчанию
         L"/helloworld/res/i/widget/simple.png"     // Путь к иконке виджета
      }

В качестве иконки можно взять аватар для этой статьи.

Логотип виджета

Сохраните этот файл в папке <сервер_TeamWox>\modules\helloworld\res\i\widget\ под именем simple.png.

5. Реализуйте методы интерфейса IWidgets.

  • CHelloWorldWidgets::Total
//+------------------------------------------------------------------+
//| Общее кол-во виджетов                                            |
//+------------------------------------------------------------------+
int CHelloWorldWidgets::Total(void)
  {
//---
   return(_countof(widgets));
  }
  • CHelloWorldWidgets::InfoGet
//+------------------------------------------------------------------+
//| Получение информации о виджете                                   |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldWidgets::InfoGet(int widget_num,WidgetInfo *info)
  {
   if(widget_num<0 || widget_num>=(int)_countof(widgets) || info==NULL) return(RES_E_INVALID_ARGS);
//---
   memcpy(info,&widgets[widget_num],sizeof(WidgetInfo));
   return(RES_S_OK);
  } 
  • CHelloWorldWidgets::IsAccessible
//+------------------------------------------------------------------+
//| Доступен ли виджет                                               |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldWidgets::IsAccessible(const Context *context,const wchar_t* widget_guid)
  {
//---
   if(context==NULL || widget_guid==NULL) return(RES_E_INVALID_ARGS);
   if(context->request==NULL)             return(RES_E_INVALID_ARGS);
//--- узнаем, что за виджет нужно показать
   if(PathCompare(L"helloworld_widget_pagetwo",widget_guid))
     {
      return(RES_S_OK);
     }
//---
   return(RES_S_OK);
  }
  • CHelloWorldWidgets::ProcessHeader. Если в конструктор класса страницы виджета передается true, то будет выводиться заголовок страницы.
//+------------------------------------------------------------------+
//| Обработка заголовка                                              |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldWidgets::ProcessHeader(const Context *context,const wchar_t *widget_guid)
  {
   TWRESULT res=RES_E_NOT_FOUND;
//--- проверки
   if(context==NULL || m_manager==NULL || widget_guid==NULL) return(RES_E_INVALID_ARGS);
   if(context->request==NULL)                                return(RES_E_INVALID_ARGS);
//--- узнаем, что за виджет нужно показать
   if(PathCompare(L"helloworld_widget_last",widget_guid))
      res=CPageWidgetSimple(true).Process(context,m_server,context->request->Path(),m_manager,0,0);
//---
   return(res);
  }
  • CHelloWorldWidgets::ProcessContent. Если в конструктор класса страницы виджета передается false, то будет отображаться содержимое страницы.
//+------------------------------------------------------------------+
//| Обработка содержимого                                            |
//+------------------------------------------------------------------+
TWRESULT CHelloWorldWidgets::ProcessContent(const Context *context,const wchar_t *widget_guid,int width,int height)
  {
   TWRESULT res=RES_E_NOT_FOUND;
//--- проверки
   if(context==NULL || m_manager==NULL || widget_guid==NULL) return(RES_E_INVALID_ARGS);
   if(context->request==NULL)                                return(RES_E_INVALID_ARGS);
//--- узнаем, что за виджет нужно показать
   if(PathCompare(L"helloworld_widget_pagetwo",widget_guid))
      res=CPageWidgetSimple(false).Process(context,m_server,context->request->Path(),m_manager,width,height);
//---
   return(res);
  }

 

2.2.2. HTTP API виджета

В качестве примера, отобразим в нашем виджете данные со страницы PageNumberTwo. Соответственно, в реализации HTTP API и пользовательского интерфейса виджета за основу возьмем код страницы PageNumberTwo и код ее шаблона.

1. В проекте создайте новую страницу для виджета. Следуя логике организации страниц модуля, поместите ее во вложенную папку widgets (аналогично страницам отчетов).

Описание страницы виджета  Реализация страницы виджета

2. В описании класса CPageWidgetSimple ограничьте количество выводимых записей на страницу до 64. Отображать вкладки нумератора в виджете нецелесообразно, а большее количество строк попросту не уместится на экране.

class CPageWidgetSimple : public CPage
  {
private:
   IServer          *m_server;
   CHelloWorldManager *m_manager;
   //---
   HelloWorldRecord  m_info[64];             // количество записей на страницу нумератора
   int               m_info_count;

3. Подобно другим страницам модуля, страница PageWidgetSimple обязательно должна реализовывать методы CPage::Process для вывода содержимого и IPage::Tag для обработки токенов, используемых в шаблонах. В вызов метода CPageWidgetSimple::Process добавьте два параметра, которые сообщают ему реальные размеры (в пикселах), которые занимает виджет.

public:
   //--- функции показа 
   TWRESULT     Process(const Context *context,IServer *server,const wchar_t *path,CHelloWorldManager *manager,int width,int height);
   bool         Tag(const Context *context,const TagInfo *tag);

4. Добавьте переключатель отображения заголовка виджета, который передается в конструктор класса страницы из менеджера виджетов. С его помощью мы сможем управлять отображением заголовка виджета в методе CPageWidgetSimple::Process.

private:
   bool              m_header_mode;

5. Объявите два метода, которые будут выводить код заголовка и код содержимого виджета, соответственно.

private:
   void              operator=(CPageWidgetSimple&) {}
   //---
   TWRESULT          ShowHeader(const Context *context,int show_count);
   TWRESULT          ShowContent(const Context *context,int show_count);

6. В конструкторе страницы виджета проинициализируйте режим отображения заголовка.

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CPageWidgetSimple::CPageWidgetSimple(bool header_mode) : m_server(NULL),m_manager(NULL),
                                                         m_info_count(0),m_header_mode(header_mode)
  {
//---
   ZeroMemory(m_info,sizeof(m_info));
//---
  }

7. Для простоты, при запросе страницы выведем лишь содержимое виджета.

//+------------------------------------------------------------------+
//| Обработка запросов                                               |
//+------------------------------------------------------------------+
TWRESULT CPageWidgetSimple::Process(const Context *context,IServer *server,
                                    const wchar_t* /*path*/,CHelloWorldManager *manager,int /*width*/,int height)
  {
//--- проверки
   if(context==NULL || manager==NULL) return(RES_E_INVALID_ARGS);
//---
   m_manager=manager;
   m_server =server;
//--- ПОКАЗ СТРАНИЦ
   if(m_header_mode)
      return(ShowHeader(context,(height/30)+1));
   else
      return(ShowContent(context,(height/30)+1));
  }

8. Реализация вывода кода заголовка. Файл с шаблоном для заголовка оставьте пустым.

//+------------------------------------------------------------------+
//| Отображение заголовка                                            |
//+------------------------------------------------------------------+
TWRESULT CPageWidgetSimple::ShowHeader(const Context *context,int show_count)
  {
//--- проверки
   if(context==NULL || m_server==NULL) return(RES_E_INVALID_ARGS);
//---
   return(m_server->PageProcess(context,L"templates\\widgets\\headers.tpl",this,TW_PAGEPROCESS_NOCACHE));
  }

9. Реализация вывода кода содержимого.

//+------------------------------------------------------------------+
//| Отображение содержимого                                          |
//+------------------------------------------------------------------+
TWRESULT CPageWidgetSimple::ShowContent(const Context *context,int show_count)
  {
   TWRESULT res=RES_S_OK;
//--- проверки
   if(context==NULL || m_manager==NULL || m_server==NULL) return(RES_E_INVALID_ARGS);
   if(context->request==NULL)                             return(RES_E_INVALID_ARGS);
//---
   m_info_count=_countof(m_info);
   if(m_info_count>show_count) m_info_count=show_count;
//--- получаем требуемое количество записей, начиная с определенной позиции
   if(RES_FAILED(res=m_manager->InfoGet(context,m_info,1,&m_info_count)))
     {
      if(res!=RES_E_NOT_FOUND && res!=RES_E_ACCESS_DENIED)
         ReturnErrorExt(res,context,"failed to get records list");
      //---
      return(res);
     }
//--- покажем
   return(m_server->PageProcess(context,L"templates\\widgets\\simple.tpl",this,TW_PAGEPROCESS_NOCACHE));
  }

10. Реализацию метода CPageWidgetSimple::Tag можно полностью скопировать из CPageNumberTwo::Tag. На выходе будем иметь токен <tw:records />, содержащий данные, отображаемые на странице PageNumberTwo.

 

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

1. Взяв за основу шаблон number2.tpl, выведем один столбец таблицы, заполнив его данными из токена <tw:records />.

<table id="helloworld_widget_simple_list" class="std_table" cellpadding="0" cellspacing="0" border="0" 
 style="width:100%;table-layout:fixed;margin:0px;">
</table>
<script type="text/javascript">
(function()
  {
   var table_obj = document.getElementById('helloworld_widget_simple_list'),
       index     = 0,
       data      = <tw:records />;
   //---
   for(var i in data)
     {
      table_obj.insertRow(index++).insertCell(0).innerHTML=['<a href="/helloworld/number_two/view/',data[i].id,'">',data[i].name,'</a>'].join('');
     }
  })();
</script>
В данный момент TeamWox JavaScript Framework пока нельзя использовать для проектирования пользовательского интерфейса виджетов, но в ближайшее время разработчики планируют добавить такую возможность.

2. Скомпилируйте проект, обновите ресурсы, запустите сервер TeamWox и откройте главную страницу.

3. Откройте панель редактирования виджетов. Как вы видите, теперь можно добавить новый пользовательский виджет для модуля Hello World.

Новый виджет для модуля Hello World

4. Перетащите его на макет главной страницы и отрегулируйте размеры.

Добавление нового виджета на главную страницу

5. Сохраните конфигурацию и обновите страницу в браузере (эту особенность следует учитывать - данные в области виджета отображаются только после перезагрузки страницы).

Виджет модуля Hello World

Теперь виджет отображает данные страницы PageNumberTwo на главной странице TeamWox!

 

3. Утилита ResPacker

Веб-проекты содержат большое количество ресурсных файлов. В системе TeamWox к ресурсам относятся изображения (*.png, *.bmp, *.jpg, *.gif, *.ico), каскадные таблицы стилей (*.css), клиентские скрипты (*.js), HTML-файлы справки (*.htm, *.html), а также шаблоны страниц (*.tpl) и прочие поддерживаемые форматы файлов. В процессе развертывания модуля у клиента перед разработчиками исходя из этого встают две задачи.

1. Синхронизация/Обновление. С каждой новой сборкой проекта необходимо проводить синхронизацию папок и файлов на серверах TeamWox у клиентов. Добавление новых папок и файлов, удаление ненужных, обновление изменившихся - все это требует множество операций, которые необходимо проверять на успешность выполнения. Соответственно, чем больше таких операций, тем больше вероятность возникновения ошибок.

2. Резервное копирование. Для выполнения резервного копирования необходимо останавливать сервер, вследствие чего работа с системой станет невозможной. Чтобы эта операция влияла на как можно меньшее количество пользователей, обычно она выполняется ночью. Но даже в это время в TeamWox могут работать пользователи, находящиеся в других часовых поясах. Для минимизации времени простоя необходимо, чтобы резервное копирование выполнялось как можно быстрее. По объективным причинам этого нельзя добиться для множества мелких файлов.

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

Для создания архивов с ресурсами модуля следует применять консольную утилиту ResPacker.exe, которая входит в состав TeamWox SDK. Это является требованием разработчиков. Имя и расширение файла строго фиксированные - res.dat - поскольку файл обрабатывается не модулем, а сервером TeamWox.

Файл res.dat имеет безусловный приоритет при обработке по сравнению с несжатыми ресурсами. В папке с DLL модуля одновременно могут находиться как res.dat, так и папка с ресурсами (например, \res), зарегистрированная в реализации метода IServer::VirtualPathRegister. Сначала будет обработан файл res.dat и только затем, (если в файле нет ресурсов, или файл поврежден) сервер будет искать ресурсы в папке \res.

В файле res.dat сохраняется структура папок и файлов (относительная от DLL модуля) - сервер обрабатывает их точно так же, как если бы они находились на диске в незапакованном виде.

 

Использование ResPacker

По умолчанию ResPacker устанавливается в папку TeamWox SDK\bin. Эта консольная утилита имеет лаконичную, но в то же время самодостаточную помощь по использованию. Утилита поддерживает два режима работы - упаковка папки в контейнер (ключ /pack) и распаковка контейнера с ресурсами (ключ /unpack). Рассмотрим их на примере модуля Hello World, DLL которого находится в папке C:\Program Files\TeamWox\modules\helloworld\, а ресурсы - во вложенной папке \res.

1. Упаковка ресурсов. В качестве аргументов указывается ключ /pack (упаковка папки), затем двоеточие, в кавычках путь к папке с DLL модуля, пробел и имя файла res.dat. В результате выполнения этой команды утилита ResPacker создает файл именем res.dat.

"C:\Program Files\TeamWox SDK\bin\respacker.exe" /pack:"C:\Program Files\TeamWox\modules\helloworld" res.dat
При упаковке ресурсов утилита ResPacker отфильтровывает файлы, не используя DLL и другие расширения, которые не относятся к ресурсам.

2. Распаковка ресурсов. Для распаковки указывается ключ /unpack, затем двоеточие, в кавычках путь к папке для распаковки ресурсов, пробел и имя файла res.dat. В результате выполнения этой команды утилита ResPacker распаковывает содержимое файла res.dat в указанную папку.

"C:\Program Files\TeamWox SDK\bin\respacker.exe" /unpack:"C:\Tmp" "C:\Program Files\TeamWox\modules\helloworld\res.dat"

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

Если команда выполняется относительно папки с DLL модуля, то вместо полного пути достаточно указать точку. Кроме того, для более удобного вызова утилиты рекомендуется добавить путь к файлу respacker.exe в переменную среды PATH.

Добавление утилиты ResPacker в переменные среды

 

Дополнительная настройка проекта Visual Studio

Применять ResPacker следует только на этапе компиляции финальной версии модуля (конфигурация Release). Для этого в настройках проекта необходимо ввести консольную команду, которая будет выполнена после компиляции модуля (Post-Build Event). Для модуля Hello World и других модулей, входящих в TeamWox SDK, эта настройка уже задана.

Упаковка ресурсов после компиляции проекта модуля Hello World

 

Заключение

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


helloworld-setupenvironment-part2-ru.zip (248.52 KB)

2011.04.18