Войти
 
 
   
 
  
Новости Notes.ру Библиотека Биржа труда Вопрос - ответ Форум Регистрация Поиск О проекте
Разделы
О Notes
Советы
Шаблоны и примеры
Литература
Презентации
 
Всё о задаче AdminP. Часть вторая   Во второй части мы завершаем рассмотрение AdminP. В ней рассмотрены запросы междоменного администрирования и способы управления функциями AdminP с помощью настроек документа сервера, команд консоли сервера, файла Notes.ini и интервалов очистки базы данных. В этой статье предполагается, что вы опытный администратор Domino и прочитали первую часть
О Notes Читать статью
 
Всё о задаче AdminP. Proxy-действия в R5 и Domino 6   Приложение к статье об административном процессе
О Notes Читать статью
 
Всё о задаче AdminP. Часть первая   Перевод классической статьи 2003-его года о задаче административного процесса (AdminP). Очень полезна для понимания работы механизма этой задачи. В первой части статьи описаны компоненты задачи AdminP, как они работают, и как их использование помогает сделать работу администратора Domino проще. Задача AdminP (сакращённо от Administration Process, Административный процесс) работает с базой административных запросов (Administration Requests, admin4.nsf)
О Notes Читать статью
 


Шаблоны и примеры

Главная   Библиотека   Шаблоны и примеры

Пример UI элемента на основе паттерна MVC

Пример UI элемента на основе паттерна MVC
Николай Каретников

Мы возьмём представленную в статье Василия Соляника >>> идею для разработки экранного элемента, позволяющего пользователю создать простой заказ на закупку оборудования. Чтобы максимально упростить систему, мы не будем выносить работу с экранным элементом в модальное окно и использовать классы MVCRoot и MVCCenter. Ограничимся лишь статическим методом, возвращающим экземпляр класса Model. Наш заказ на закупку будет состоять из произвольного* числа строк, каждая из которых содержит 3 элемента:

    1. Название товара
    2. Число, к которому необходимо закупить товар
    3. Количество единиц товара
Экранный элемент будет содержать
  • кнопки "Добавить", "Изменить", "Удалить"
  • поля для ввода и изменения данных строки заказа
  • общее поле, отображающее заказанные строки.
Предполагаемый вид экранного элемента показан на рисунке


Реализация
Описанную в статье базу данных Вы можете скачать >>>.
Для простоты восприятия база содержит только необходимые элементы:
    • форма заказа
    • библиотека, реализующая MVC
    • библиотека, реализующая парсинг строки (заказ хранится в многоэлементном поле-хранилище). Формат строки приведен ниже:
[Tag0]Value0|[Tag1]Value0...|[TagN]Value0
[Tag0]Value1|[Tag1]Value1...|[TagN]Value1
...
[Tag0]ValueM|[Tag1]ValueM...|[TagN]ValueM
На форме всего 2 вспомогательных поля (внимательный читатель увидит, что на самом деле полей 3. Подробнее о третьем поле читайте в конце статье):
одно - xRequestData_Storage, это хранилище, которым оперирует класс Model,
второе - xRequestData_RezList.Source, это источник данных для отображаемого поля, этим полем оперирует класс View

Кнопки содержат вызовы
Изменить: getStaticControler().onSubmitItem()
Добавить: getStaticControler().onAddItem() 
Удалить: getStaticControler().onDeleteItem()
По событию PostRecalc формы, вызывается метод getStaticControler().onRecalc()
Здесь метод getStaticControler определён как глобальный метод, возвращающий единственный экземпляр класса Controler.
Диаграмма классов показана на следующем рисунке. Ниже дано подробное описание каждого из методов 5 классов, представленных на нём.

Границы ответственности классов
Класс View не должен ничего знать о модели и классе Controler, он реализует задачи по установке значений в поля NotesDocument и обновлении UI документа. Класс View для установки и получения значений в трёх редактируемых полях, составляющих отдельный итем заказа, использует дополнительный класс ItemFieldManager.

Класс Model не знает ничего о классе View и Controler, инкапсулируя логику работы с данными, он не представляет, как будут отображены эти данные и в какой момент. Используя коллекцию итемов, он работает с ней с помощью простейших методов: обновить итем, удалить итем, добавить итем, получить номер итема, получить ближайший номер итема.
Класс Controler знает всё о View и Model, именно в Controler заключена логика работы MVC. Выполняя простейшие операции из View и Model, Controler как из отдельных кирпичей собирает в единой целое каркас MVC. В результате получается логичный, легко читаемый и дополняемый код (о том, как добавить функциональности к инструментарию, не дописав при этом лишь пару строк, читайте в конце статьи)
Class Controler
методы кнопок управляют классами View и Model в соответствии с паттерном.
+ onAddItem()
проверяет готовность (корректность заполнения 3х полей, формирующих один итем) и, в случае успеха, существование текущего итема в поле-хранилище. В случае, если итем не соответствует никакой строке заказа в хранилище, дает команду классу Model добавить туда текущий итем, затем последовательно выполняет 4 метода класса View:
    1. обновляет информацию в поле-хранилище.
        Call meView.setStorageSource( getCollectionStorageFormat( meModel.getCollection() ) )
        Метод получает обновленную коллекцию всех итемов, преобразует ее к хранимому формату и записывает с помощью класса View преобразованную коллекцию в поле-хранилище.
    2. обновляет информацию в поле-источнике данных
      Call meView.setDisplaySource( getCollectionDisplayFormat( meModel.getCollection() ) )
        Метод получает обновленную коллекцию всех итемов, преобразует ее к отображаемому формату и записывает с помощью класса View преобразованную коллекцию в поле-источник данных для отображения.
    3. обновляет текущий UI документ
    4. устанавливает в поле-общем списке отображаемых данных, значение соответствующее добавленному итему
В конце класс Controler обновляет значение номера текущего итема, получая от класса Model порядковый номер добавленного итема

+ onSubmitItem()
проверяет готовность (корректность заполнения 3х полей формирующих один итем) и, в случае успеха, существование текущего итема в поле хранилище. В случае, если итем не соответствует никакой строке заказа в хранилище, дает команду классу Model поместить на место текущего итема обновленный итем, затем последовательно выполняет 4 метода класса View:
1. обновляет информацию в поле-хранилище,
2. обновляет информацию в поле-источнике
3. обновляет текущий UI документ
4. устанавливает в поле-общем списке отображаемых данных, значение соответствующее изменённому итему

+ onDeleteItem()
если номер текущего итема равен 0, значит в данный момент не выделен ни один итем. Это означает, что пользователь или ещё ничего не добавил или уже удалил всё, что добавил ранее. В таком случае метод прекращает работу. Иначе:
из коллекции итемов класса Model удаляется итем с номером, соответствующим текущему выделенному итему. Затем последовательно выполняется 3 метода класса View
1. обновляется информация в поле-хранилище полученной от класса Model и преобразованной к хранимому формату, коллекцией всех итемов,
2. обновляется информация в поле-источнике данных для отображения, полученной от класса Model и преобразованной к отображаемому формату, коллекцией всех итемов
3. обновляется текущий UI документ
Получает новый номер текущего итема (нам необходимо установить курсор на итем, ближайший к удаленному). Если найденный номер больше 0, значит в списке еще остался минимум один итем, и именно на него мы будем устанавливать выделение, и именно с него мы будем брать информацию для установки в 3 редактируемых поля, формирующих итем

это выполняется двумя методами класса View
meView.highlightItem
meView.showItem

+ onRecalc()
вызывается из PostRecalc события формы. Если обновление вызвано перемещением по списку заказа, то вызывается метод onSelectItem, иначе - выход из метода. В данном примере на всей форме лежит единственный UI элемент, поэтому класс Controler прекращает работу по обновлению документа в случае, если обновление не вызвано подконтрольными ему элементами. Когда же на форме находится более одного UI элемента работу с ними лучше организовать с использованием паттерна Fasade. О формах, содержащих множественные элементы UI, читайте подробно в статье Василия Соляника >>>.

+ onSelectItem()
В этом месте нам необходимо обновить номер текущего итема и установить в 3 редактируемых поля информацию из выбранного пользователем итема.

далее следуют 2 приватных метода и 2 приватных свойства
- ItemNotSaved As Boolean
рассматривается в конце статьи
- isAlreadyExist as Boolean
возвращает True если текущий итем совпадает с каким-либо из итемов занесенных пользователей ранее, иначе False
- getCollectionStorageFormat
возвращает Variant, содержащий набор строк, подготовленных для занесения в поле-хранилище
- getCollectionDisplayFormat
возвращает Variant, содержащий набор строк, подготовленных для занесения в поле-общий список отображаемых данных

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

Class ItemFieldManager
реализует логику работы с 3 полями, составляющими строку заказа. Отвечает за проверку корректности заполнения строки заказа. По переданному внутрь метода showItem() расставляет в каждое из полей UI элемента соответствующие значения. Работа с тремя полями была специальна возложена на отдельный класс. В случае, если количество полей изменится можно будет подменить ItemFieldManager другим классом, дополнить класс Item необходимыми методами, а каркас MVC оставить нетронутым.

Рассмотрим теперь 2 оставшихся класса View и Model

Class Model
Повторюсь, что задачей этого класса является работа с данными в отрыве от того, как эти данные будут в конечном счете записаны в документ. Оперировать мы будем не со строчками, а с итемами и их коллекцией. Для создания коллекции итемов воспользуемся классом Vector, который предоставляет необходимые методы для работы с коллекциями любых объектов. Нам придется лишь написать свой метод сравнения объектов. Рассмотрим весь интерфейс класса по порядку:
+ getCollection() as Vector
возвращает коллекцию итемов. Это необходимо классу Controler, для получения 2 вариантов значений типа Variant. Один - для сохранения в поле-хранилище, другой - для сохранения в поле-источник данных для отображения общего списка заказа
+ updateItem( xItemNumber2Update As Integer, x2BEaNewItem As Item )
вызов соответствующего метода класса Vector изменяет итем с номером xItemNumber2Update на значение итема x2BEaNewItem
+ addItem( xItem As Item )
вызов соответствующего метода класса Vector добавляет итем xItem к коллекции
+ deleteItem( xItemNumber As Integer )
вызов соответствующего метода класса Vector удаляет итем xItem из коллекции
+ getNthItem( xNumber As Integer ) As Item
вызов соответствующего метода класса Vector возвращает итем с номером xNumber
+ getItemNumber( xItem As Item )
возвращает номер итема, поданного на вход. Простейший поиск по коллекции
+ getTheClosestIndex( xIndex As Integer ) As Item
возвращает номер, ближайший к поданному на вход. Используется в момент удаления итема для нахождения номера итема, на который будет установлен курсор.

Class View
Методы этого класса очень просты, так же, как и методы класса Model. Всё, что мы ожидаем от View, - это обновление документа, помещение в некоторые поля уже подготовленных данных, и один метод, который возможно не вполне свойственен ему, но в данной ситуации он позволяет сэкономить некоторое количество кода. Речь идет о методе, возвращающем номер итема, выбранного пользователем. Можно было бы возвращать строку, а в классе Controler формировать из строки итем, чтобы затем, обращаясь к модели, получать номер, но я решил поступить здесь по-другому. Метод использует вызов Evaluate(@Member) для нахождение нужного номера. 100%-ные фанаты ООП, возможно, осудят меня, но решение, использованное в этом методе достаточно красивое и не создает необходимости расширять интерфейс класса Item
+ setStorageSource( xValue As Variant)
проставляет в поле-хранилище значения, подаваемые на входе.
+ setDisplaySource( xValue As Variant)
проставляет в поле-источник данных для отображения значения, подаваемые на входе.
+ getItemFieldManager() As ItemFieldManager
нужен классу Controler для получения доступа к экземпляру класса Item, составленному из текущих значений 3 полей. Эта информация используется для сохранения уникальности данных в хранилище
+ showItem(xItem As Item)
дает команду классу ItemFieldManager на обновление значений трех редактируемых полей данными, извлеченными из итема, подаваемого на входе
+ highlightItem( xItem as Item)
подсвечивает в поле-общем списке всех заказов строку, соответствующую значению итема, подаваемого на входе

+ refreshUIDocument
обновление интерфейсного документа
+ getSelectedItemNumber() as Integer
метод возвращает номер выделенного итема

Итоги и возможности улучшения UI элемента
Теперь давайте посмотрим, что получилось, и можно ли что-то улучшить.
С помощью подхода, описанного паттерном MVC мы быстро создали удобный инструмент работы со массивом значений типа [ключ1]значение|[ключ2]значение... Добавление к описанной модели новых возможностей выполняется относительно просто**. Попробуем, например, реализовать следующую задачу:
Начальные условия:
Менеджер, работающий с программой, ввел 2 новых итема. Выделен второй итем. Пользователь вводит другие значения в поля для редактирования итема, и в этот момент ему звонит клиент. После телефонного звонка пользователь возвращается к программе, но по забывчивости вместо клика на кнопку "Изменить", кликает на первой строке заказа.
Задача:
Сделать так, чтобы программа отловила это событие и оповестила пользователя о том, что он может потерять изменения, предложив ему 2 возможности для продолжения:
  • Вернуться к редактированию
  • Продолжить без изменений
Решение:
Что нам необходимо?
  • Знание того, с какой строчки пользователь ушел.
Это знание хранится в классе Controler в переменной meCurrentItemNumber
  • Знание того, что тот итем, с которого пользователь ушел, не был занесен в хранилище.
Здесь нам поможет метод getItemNumber класса Model. Он возвращает 0, если итема, поданного ему на вход, не окажется в хранилище, и число от 1 до N, соответствующее порядковому номеру итема в хранилище в случае, если итем существует. Допишем в классе Controler свойство ItemNotSaved As Boolean
Dim aItem As Item
Set aItem = meView.getItemFieldManager().getItem()
If aItem Is Nothing Then ItemNotSaved = False Else ItemNotSaved = ((meModel.getItemNumber(aItem)) =  0)

Как это работает:
Пользователь кликает на некоторой строке спискового поля, происходит событие PostRecalc, вызывается изменённый метод getStaticRequestControler().onRecalc(), в котором теперь мы отследили ситуацию с изменённым, но не сохранённым итемом
If ItemNotSaved Then
здесь мы предлагаем пользователю 2 варианта развития событий. Если пользователь возвращается к редактированию, то нам необходимо изменить значение спискового поля на отображаемый формат итема, с которого ушёл пользователь. Это делается элементарно
Call meView.highlightItem( meModel.getNthItem(meCurrentItemNumber) )
Иначе мы переписываем значения редактируемых полей новыми данными, извлечёнными из хранилища.
Call Me.onSelectItem()
Все!

Бантики
Чтобы придать списку вид таблицы, мы укажем в списковом поле моноширинный текст и вставим в функцию отображения данных для просмотра, алгоритм, дополняющий точками короткие значения или урезающий последние 3 символа от длинных значений и помещающий на их место 3 точки. Изменяя константу указанную в методе getFormatedSubstring, Вы можете подгонять размер столбцов под ширину поля. Вы можете отойти от использования константы и менять "ширину столбцов" динамически, в зависимости от длины данных в хранилище.

Заключение
Конечно, пример, приведённый в данной статье, является очень простым и не лишен недостатков (в том числе с точки зрения UI. Вы можете его модернизировать, например, так, чтобы при выделении строки заказа курсор автоматически становился на первое поле и его содержимое выделялось. Выводить предупреждения о неверных значениях в полях всплывающими сообщениями и т.п. и т.д).
Пожалуйста, высказывайтесь по любой мысли, затронутой в статье, причем интересуют разные мнения, особенно критические замечания!
Спасибо!

Примечания
* произвольный настолько, насколько позволят объемы данных хранящихся в обычном текстовом поле и мощность компьютера на котором будет выполняться код. При увеличении количества строк заказа, будет наблюдаться уменьшение скорости обработки данных.
** не все так уж красиво. Попробуйте, например, изменить код так, чтобы дать пользователю еще и возможность добавить измененные данные к общему списку. Метод getStaticRequestControler().onAddItem() приведет к RunTime-ошибке из-за попытки вызова вложенных событий формы.

Материалы по теме на Notesnet.ru
О пользе ООП при проектировании систем на платформе Lotus Notes/Domino >>>
Использование паттерна MVC в модальных диалоговых окнах >>>
Паттерны. Практикум программирования >>>
 
  Опубликовано — 06/22/2007 |    



Добавить комментарий
Имя * :
e-mail
Комментарий * :
Код подтверждения * :

Мероприятия
Пресс-релизы
Биржа труда
Последнее на форуме
 
А так же:
Как удалить профиль?
16.04.2016 00:08:51
Скопировать в буфер поле документа
24.05.2015 08:55:52
Импорт DXL-описания документов в Lotus Domino. Одноимённые поля
16.04.2015 16:49:58
 
© LOGOSPHERE.RU