Введение
В первой части статьи мы рассмотрели основные вопросы в области настройки окружения пользовательских модулей. Во второй части мы поговорим об интеграции пользовательского модуля в главную страницу TeamWox, для чего нам потребуется реализовать ряд интерфейсов.
1. Менеджер кэширования советов
1.1. Интерфейсы
1.2. Пример расширения списка советов
2. Виджеты
2.1. Интерфейс IWidgets
2.2. Пример реализации пользовательского виджета
3. Утилита ResPacker
1. Менеджер кэширования советов
Модуль "Home" (первая вкладка по-умолчанию среди модулей из стандартной поставки) отображает различную информацию в виде коротких однострочных сообщений внизу главной страницы 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
являются взаимоисключающими.
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 не отображается сразу, прокрутите ленту с советами, нажимая кнопку до тех пор, пока не появится нужный совет.
2. Виджеты
Как известно, виджеты в системе TeamWox - это графические элементы пользовательского интерфейса модуля "Home" в виде прямоугольных областей. В каждом таком прямоугольнике отображается информация из определенного модуля.
Пользовательский интерфейс модуля "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, по которому запрашивается необходимая страница модуля. Текстом ссылки при этом является поле Например, может быть два виджета, первый из которых отображает главную страницу модуля, а второй - конкретный раздел (как это реализовано в виджетах Последние задания и Избранные задания). |
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>
2. Скомпилируйте проект, обновите ресурсы, запустите сервер TeamWox и откройте главную страницу.
3. Откройте панель редактирования виджетов. Как вы видите, теперь можно добавить новый пользовательский виджет для модуля Hello World.
4. Перетащите его на макет главной страницы и отрегулируйте размеры.
5. Сохраните конфигурацию и обновите страницу в браузере (эту особенность следует учитывать - данные в области виджета отображаются только после перезагрузки страницы).
Теперь виджет отображает данные страницы 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
.
Дополнительная настройка проекта Visual Studio
Применять ResPacker
следует только на этапе компиляции финальной версии модуля (конфигурация Release
). Для этого в настройках проекта необходимо ввести консольную команду, которая будет выполнена после компиляции модуля (Post-Build Event
). Для модуля Hello World и других модулей, входящих в TeamWox SDK, эта настройка уже задана.
Заключение
В двух частях этой статьи мы рассмотрели немаловажные вопросы, которые возникают при разработке пользовательских модулей для системы TeamWox. Вы узнали как правильно настроить окружение модуля, что такое ресурсы и как их эффективно хранить и обслуживать, как добавляются виджеты и советы вместе с пользовательскими командами.
- Как добавить готовый модуль в TeamWox
- Как добавить страницу в модуль TeamWox
- Построение пользовательского интерфейса
- Взаимодействие с СУБД
- Создание пользовательских отчетов
- Файловое хранилище - Часть 1
- Файловое хранилище - Часть 2
- Настройка окружения пользовательских модулей - Часть 1
- Настройка окружения пользовательских модулей - Часть 2
- Поиск и фильтрация - Часть 1
- Поиск и фильтрация - Часть 2
- Настройка "Онлайн-консультанта" на вашем сайте
- Как создать дополнительный языковой пакет для TeamWox
2011.04.18