В этой части мы научимся вставлять простейшую таблицу в окно qooxdoo.
Общая часть
Задание
Необходимо реализовать список пользователей с редактированием с соблюдением следующих условий:
- Для пользователя хранить следующие данные:
- фамилия, имя, отчество;
- телефон;
- логин, пароль;
- описание;
- Для хранения данных использовать postgres;
- В качестве сервера использовать mochiweb;
- Для вывода списка пользователей использовать таблицу qooxdoo;
- В окне вывода списка предусмотреть следующие кнопки: добавить, удалить, редактировать;
- При нажатии на кнопку «Редактировать», выводить окно с формой редактирования данных пользователя. В окне предусмотреть кнопки «Сохранить» и «Выход». После нажатия на кнопку «Сохранить» обновить данные о пользователе в базе данных.
- При нажатии на кнопку «Добавить» выводить окно с формой редактирования данных пользователя (аналогично п.6) с пустыми полями. Кнопка «Сохранить» должна быть недоступна, пока не введено фамилия пользователя.
- При нажатии на кнопку «Удалить» выводить окно с подтверждением удаления пользователя. После подтверждения – удалить запись о пользователе из базы.
Предварительные условия
Считаем, что у пользователя уже установлены и настроены следующие программы: Postgress, mochiweb, qooxdoo, epgsql (Erlang PostgreSQL Database Client).
В качестве операционной системы я буду использовать Ubuntu. Хотя это не принципиально.
Создаем базу данных и таблицу с данными о пользователе
План простой: создать базу данных для экспериментов под названием dbUsers, создать в ней таблицу users с нужными полями. Запускаем терминал.
1. Создаем базу.
sudo -u postgres createdb dbUsers
2. Создаем пользователя с правами суперюзера (естественно, имя пользователя и пароль в примере условные)
sudo -u postgres createuser faist -P
Enter password for new role:
Enter it again:
Shall the new role be a superuser? (y/n) y
3. Подсоединяемся к базе
psql -d dbUsers –U faist
4. После успешного соединения, создаем таблицу.
Запускаем редактор \е.
Набираем запрос:
create table users (id serial primary key, surname varchar(50), name varchar(50),
patronymic varchar(50), telephone varchar(12),login varchar(50),
password varchar(50),note varchar(250));
Выполняем запрос.
Проверяем наличие нашей таблицы: \d – выведет список таблиц, в том числе нашу.
Выполним запрос:
select * from users;
Ответ должен быть таким:
dbUsers=# select * from users;
id | surname | name | patronymic | telephone | login | password | note
----+---------+------+------------+-----------+-------+----------+------
(0 rows)
Таблица и база готовы к наполнению.
Закрываем psql.
Создаем приложение на qooxdoo
Сначала определимся с именем приложения и местонахождением. Назовем приложение users а папка должна быть внутри корневой папки веб сервера mochiweb. Переходим в папку qooxdoo/tool/bin и запускаем генератор приложения.
./create-application.py --name=users --out=./../../../../job/js
>>> Copy skeleton into the output directory: ./../../../../job/js/users
>>> Patching file './../../../../job/js/users/Manifest.json'
>>> Patching file './../../../../job/js/users/generate.py'
>>> Patching file './../../../../job/js/users/config.json'
>>> Patching file './../../../../job/js/users/source/index.html'
>>> Patching file './../../../../job/js/users/source/class/users/Application.js'
>>> Patching file './../../../../job/js/users/source/class/users/theme/Decoration.js'
>>> Patching file './../../../../job/js/users/source/class/users/theme/Font.js'
>>> Patching file './../../../../job/js/users/source/class/users/theme/Color.js'
>>> Patching file './../../../../job/js/users/source/class/users/theme/Theme.js'
>>> Patching file './../../../../job/js/users/source/class/users/theme/Appearance.js'
>>> Patching file './../../../../job/js/users/source/class/users/test/DemoTest.js'
>>> Patching file './../../../../job/js/users/source/class/users/simulation/DemoSimulation.js'
>>> DONE
Приложение создано.
Перейдем в папку приложения и запустим
/job/js/users$ ./generate.py source
Далее открываем в браузере файл users/source/Index.html. Появится знакомая по предыдущим экспериментам кнопка.

Тестируем mochiweb
Давайте увидим эту кнопку через mochiweb. Запустим mochiweb и откроем файл по следующему пути http://localhost:8080/users/source/index.html. Скорее всего, у вас ничего не получится, потому что будет недоступна ссылка на библиотеки qooxdoo. Добавьте линки на qooxdoo. Я создал в корневой папке web подкаталог deps и добавил туда ссылку на qooxdoo. Соответственно в файле users/config.json исправил путь к qooxdoo следующим образом: "QOOXDOO_PATH" : "../../deps/qooxdoo". И, на всякий случай, поменяйте путь в файле generate.py. Затем снова генерируем ./generate.py source.
Теперь в браузере все должно быть хорошо.
План дальнейшей работы
Далее идем по пути, похожему на тот, что описан в предыдущих статьях.
- Создать класс главного окна приложения.
- В класс приложения добавить вызов главного окна, полюбоваться на него.
- Разместить в главном окне таблицу и отформатировать ее.
- Добавить в главное окно кнопки Добавить, Изменить, Удалить.
- Создать обработчики кнопок.
- Создать диалог редактирования записи о пользователе.
- Создать класс, отвечающий за бизнес-логику, который будет обрабатывать наши запросы, пока без обращения к базе данных.
- Когда все заработает, в классе бизнес-логики реализовать ajax запросы к mochiweb.
- Реализовать обработку запросов в mochiweb с обращением к базе данных.
- Протестировать наше приложение.
Вы можете спросить: зачем мы реализуем какие-то кнопки, не проще редактировать значение прямо в ячейке и сохранять новые значения по нажатию клавиши «Enter»? Отвечаю: многолетний опыт работы с пользователями говорит мне, что редактирование данных в ячейке в большинстве случаев - плохой путь. Пользователь может случайно изменить данные в ячейке, не желая этого. И в этом случае нам придется выводить всякие запросы о том, что данные изменились, хотите ли вы их сохранить и пр. И еще, пользователь ожидает более четких разделений функций просмотра и редактирования. Когда мы выводим форму для редактирования, пользователь понимает, что он реально может изменить данные и подходит к делу более ответственно. Впрочем, это только мои мысли.
При реализации всего не забываем о переводе, т.е. будем разрабатывать приложение на английском языке, затем переведем на русский.
Класс главного окна приложения
Создаем код класса главного окна с панелью инструментов и кнопками
На первом этапе создадим класс главного окна с разметкой, панелью инструментов и кнопками.
Как мы уже делали это, создадим файл MainWindow.js со следующим кодом.
qx.Class.define("users.MainWindow",
{
extend : qx.ui.window.Window,
construct : function()
{
this.base(arguments, " The information on users");
//скрываем кнопки окна
this.setShowClose(false);
this.setShowMinimize(false);
this.setShowMaximize(false);
//добавляем форматирование по сетке
//и делаем растягивающейся первую ячейку второй строки
//в которой будет находиться таблица
var layout = new qx.ui.layout.Grid(0, 0);
layout.setRowFlex(1, 1);
layout.setColumnFlex(0, 1);
this.setLayout(layout);
//Добавляем панель инструментов
var toolbar = new qx.ui.toolbar.ToolBar();
this.add(toolbar, {row: 0, column: 0});
//Добавляем кнопки
//Кнопка добавления информации о новом пользователе
var addButton = new qx.ui.toolbar.Button(this.tr("Add"));
addButton.setToolTipText(this.tr("Add the information on the new user."));
toolbar.add(addButton);
//Кнопка редактирования информации о пользователе
var editButton = new qx.ui.toolbar.Button(this.tr("Edit"));
editButton.setToolTipText(this.tr("Edit the information on the chosen user."));
toolbar.add(editButton);
//Кнопка удаления записи
var deleteButton = new qx.ui.toolbar.Button(this.tr("Delete"));
deleteButton.setToolTipText(this.tr("Delete the information on the chosen user."));
toolbar.add(deleteButton);
}
});
Напомню, что делает этот код. Мы создаем класс окна, наследуя его от qx.ui.window.Window. В конструкторе класса мы сначала скрываем системные кнопки окна, затем добавляем форматирование для элементов управления. Панель инструментов мы размещаем в первой ячейке первого столбца сетки. В панель управления мы добавляем кнопки, для которых указаны надписи и всплывающие подсказки. Все названия и подсказки мы заключаем в this.tr() для дальнейшего перевода.
Итак, начало положено. Теперь заменим в файле Application.js следующий код:
// Create a button
var button1 = new qx.ui.form.Button("First Button", "users/test.png");
// Document is the application root
var doc = this.getRoot();
// Add button to document at fixed coordinates
doc.add(button1, {left: 100, top: 50});
// Add an event listener
button1.addListener("execute", function(e) {
alert("Hello World!");
});
На:
var mainWnd = new twitter.MainWindow();
mainWnd.moveTo(30, 20);
mainWnd.open();
Генерируем код и проверяем (здесь и в дальнейшем я понимаю под этим выполнение команды ./generate.py source и просмотр результата в браузере). У вас получится что-то похожее на это:

Страшненько, но ничего, в дальнейшем мы все украсим.
Пора добавить таблицу.
Добавление таблицы
С таблицами мы еще не работали, поэтому в этой части есть кое-что новое.
Подробнее таблицы мы рассмотрим в одной из следующих статей, а пока нам надо знать следующее: для создания таблицы необходимо создать модель данных, которую мы свяжем с таблицей.
// table model
var tableModel = this.__tabMod = new qx.ui.table.model.Simple();
tableModel.setColumns([this.tr("ID"), this.tr("Surname"), this.tr("Name"),
this.tr("Patronymic"), this.tr("Telephone"), this.tr("Login"),
this.tr("Password"), this.tr("Note")]);
// table
var table = new qx.ui.table.Table(tableModel).set({
decorator: null
});
this.add(table, {row: 1, column: 0});
Генерируем и проверяем приложение.

Пока все идет как надо. Мы видим таблицу с нужными названиями столбцов. В таблице нет записей. Для экспериментов добавьте следующую строку перед созданием таблицы
tableModel.setData([[1,"Иванов","Иван","Иванович","123456","ivan","pwd","Нормальный пацан"],
[2,"Петров","Петр","Петрович","234567","petr","pwd1","Настоящий мужик"]
]);
// table
var table = new qx.ui.table.Table(tableModel).set({ ….
Мы добавили данные для строк, при этом первый столбец мы сделали числовым для правильного выравнивания значения в столбце.
Добавляем обработку событий
В окне нужно обработать три события: добавление, редактирование и удаление информации о пользователе. Предлагаю подумать о том, в каком классе мы будем обрабатывать эти события? Если вы уже все знаете, можете пропустить пару абзацев.
Напоминаю вам идею об отделении интерфейса от бизнес-логики. Реализация этой идеи позволяет решить, например, проблему повторного использования кода. Без переписывания интерфейса, подключив другой класс обработки логики, можно создать другое приложение. Мы будем использовать эту идею и в этом приложении.
Обработчики событий можно написать в классе MainWindow, но это противоречит идеи отделения интерфейса от бизнес-логики. Бизнес-логика будет описана в отдельном классе. Нам нужно будет создать объект этого класса, прежде чем обратиться к его методам (если они не статические, но это не наш случай). Где мы будем создавать объект класса? Если создать этот объект в классе MainWindow, установится зависимость между бизнес-логикой и интерфейсом. В этом случае класс MainWindow нельзя без переделок использовать в другом приложении. А какой класс характеризует именно наше приложение? Конечно класс Application. Именно этот класс отвечает за создание объектов классов нашего приложения и их взаимодействие.
Хорошо, обработчики событий создаем в классе Application. Возникает другая сложность: класс Application ничего не знает о наших кнопках и об их состояниях. У него есть объект класса MainWindow, в котором находятся кнопки. Что делать? Плохой путь: сделать доступными кнопки класса MainWindow снаружи. Хороший путь: объявить, что у класса MainWindow три события (по количеству кнопок), связать эти события с обработчиками кнопок, написать обработчики этих событий в классе Application. Именно этим мы сейчас и займемся.
Первый шаг: описываем события класса MainWindow.
events :
{
"NewUser" : "qx.event.type.Event",
"EditUser" : "qx.event.type.Data",
"DeleteUser" : "qx.event.type.Data"
}
Мы создали три события: первое – создание нового пользователя не подразумевает передачу информации из таблицы, а два оставшихся должны передавать информацию из выделенной строки таблицы для редактирования и удаления. Поэтому типы событий разные.
Второй шаг: добавляем обработку событий кнопок и таблицы и связываем их с событиями окна.
Первая кнопка – добавление информации о новом пользователе. В данном случае данных еще нет, просто сообщаем о том, что мы хотим вывести форму для заполнения информации о новом пользователе. Поэтому используем firEvent.
addButton.addListener("execute", function() {
this.fireEvent("NewUser");
}, this);
Вторая кнопка – редактирование информации о пользователе. В этом случае мы должны взять информацию из текущей строки таблицы и передать ее для редактирования. Используем fireDataEvent.
editButton.addListener("execute", function() {
if(table.getFocusedRow() != null)
this.fireDataEvent("EditUser",this.__tabMod.getRowData(table.getFocusedRow()));
}, this);
Третья кнопка – удаление информации о пользователе. В этом случае достаточно передать только значение из первой, ключевой, колонки выделенной строки. Но мы хотим вывести пользователю осмысленный запрос об удалении информации и поэтому передаем данные всей строки.
deleteButton.addListener("execute", function() {
if(table.getFocusedRow() != null)
this.fireDataEvent("DeleteUser",this.__tabMod.getRowData(table.getFocusedRow()));
}, this);
Обработчики событий кнопок лучше размещать рядом с определением кнопок, чтобы было удобно искать связанный код.
А теперь добавим обработчики событий окна в класс Application. Пока реализуем вывод отладочной информации.
mainWnd.addListener("NewUser", function() {
this.debug("NewUser");
}, this);
mainWnd.addListener("EditUser", function(e) {
this.debug("EditUser: " + e.getData());
}, this);
mainWnd.addListener("DeleteUser", function(e) {
this.debug("DeleteUser: " + e.getData());
}, this);
Сгенерируем приложение и проверим вывод отладочной информации при нажатии на кнопки.
Приложение будет выглядеть вот так:

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