Сегодня я постараюсь показать, как можно использовать возможности недавно вышедшей библиотеки QooxDoo 0.6 RC1 в сочетании с Ruby on Rails.
Зачем (небольшое предисловие)
Для создания RIA-приложений можно использовать различные инструменты. В Ruby on Rails есть встроенная поддержка AJAX на базе библиотек Prototype и Scriptaculous, с их помощью можно делать достаточно гибкие интерфейсные возможности используя JavaScript и движок браузера. Недавно вышедший WebORB уже позволяет стыковать Ruby on Rails с Adobe Flex 2, с его помощью можно делать навороченные интерфейсы используя собственный движок Flex на базе Flash и ActionScript 3.
И у того, и у другого подхода есть как плюсы, так и минусы. В качестве плюсов Prototype можно назвать легкость в использовании, гибкость, малый вес и независимость от установленных компонентов (например, Flash). В качестве минусов - наличие всего лишь базового функционала для создания интерактивных интерфейсов.
Основной плюс Flex - это то, что Flex, по сути, самостоятельный framework с огромным набором компонентов, продуманным и задокументированным API. Минусы - отсутствие open-source среды разработки и зависимость от Flash-проигрывателя с достаточно ограниченными возможностями для интеграции с браузером (хотя бы тот факт что я не могу отобразить внутри Flex любой нужный мне HTML - это для моих задач просто неприемлимое ограничение).
Оба эти подхода стали для меня неприемлимы когда возникла задача создать интерфейс, в котором широкий набор компонентов сочетался бы с гибкостью в представлении информации. В частности, встал вопрос об импортировании готовых HTML-документов с произвольным содержанием и отображении их внутри интерфейса. От Prototype пришлось отказаться, т.к. он примитивен, а от Flex - в связи с тем, что он не умеет отображать внутри себя хоть сколько-нибудь сложный HTML. Пришлось искать третий вариант.
QooxDoo
QooxDoo по набору визуальных компонентов уже вполне сопоставим с Flex. Во всяком случае, он гораздо ближе подошел к званию полноценного JavaScript-фрэймворка чем Prototype. С его помощью уже можно делать достаточно навороченные интерфейсы, работающие с серверной стороной. Основной плюс - это то, что он не требует никаких дополнительных компонентов и работает используя исключительно возможности браузера. Основной минус - большой вес библиотеки (код всей библиотеки весит примерно 950 килобайт, плюс еще заргужаемые картинки и иконки для интерфейса).
Для меня он хорош тем, что может полноценно работать с HTML, в том числе отображать его внутри себя или себя внутри него. Это очень удачно вписывается в концепцию конкретной задачи, которую мне необходимо решить.
Первые шаги
При выборе идеи для демонстрации возможностей QooxDoo, я не буду далеко ходить и возьму за основу идею статьи Rolling with Ruby on Rails, уже успевшей стать классическим документом для быстрого ознакомления с Ruby on Rails (если еще не читали - это большое упущение). Будем создавать книгу рецептов.
Итак, начнем. Создаем новый проект:
rails qooxdoo_cookbook
Заходим в папку проекта, создаем нашу основную модель:
ruby script/generate model Recipe
В файле db/migrate/001_create_recipes.rb прописываем следующее:
class CreateRecipes < ActiveRecord::Migration
def self.up
create_table :recipes do |t|
t.column :title, :string
t.column :description, :string
t.column :instructions, :text
t.column :created_at, :datetime
end
end
def self.down
drop_table :recipes
end
end
Это наши инструкции для создания таблицы в базе данных проекта. Создаем БД и запускаем миграцию:
rake migrate
Все, таблица у нас есть, данные есть куда сложить. Теперь делаем контроллер:
ruby script/generate controller Recipes
Файлы контроллера созданы. В файле app/controllers/recipes_controller.rb прописываем:
class RecipesController < ApplicationController
scaffold :recipe
end
Теперь мы можем запустить сервер и добавить некоторые данные в нашу таблицу:
ruby script/server
Вбиваем некоторые данные в таблицу:

Теперь нам нужно к нашему проекту пристыковать QooxDoo. Для этого мы из скачанного архива (для быстрого старта лучше качать готовый build, чтоб не возиться с компиляцией) в папку public нашего проекта полность копируем папку framework и переименуем ее в qooxdoo (для наглядности). Папочка эта весьма внушительная по размеру, в основном из-за картинок.
Теперь мы создаем интерфейс. В контроллере RecipesController (app/controllers/recipes_controller.rb) мы добавляем пустой метод:
class RecipesController < ApplicationController
scaffold :recipe
def index
end
end
В шаблоне для этого метода (app/views/recipes/index.rhtml) прописываем следующее:
<html>
<head>
<script type="text/javascript" src="/qooxdoo/script/qx.js"></script>
<script type="text/javascript" src="/javascripts/application.js"></script>
</head>
<body>
</body>
</html>
В файле public/javascripts/application.js мы инициализируем наш QooxDoo-интерфейс:
qx.core.Init.getInstance().defineMain(function() {
// Получаем основной документ
var rootDoc = qx.ui.core.ClientDocument.getInstance();
// Создаем заголовок формы и добавляем его в основной документ
var recipeLabel = new qx.ui.basic.Label("Recipes", "R");
with(recipeLabel){
setLeft(10);
setTop(5);
}
rootDoc.add(recipeLabel);
// Создаем элемент "список", в который мы будем принимать данные с сервера
var recipeList = new qx.ui.form.List();
with(recipeList){
setLeft(10);
setTop(20);
setWidth(400);
setHeight(200);
}
rootDoc.add(recipeList);
// Создаем кнопку для отправки запроса на сервер
var listRecipesButton = new qx.ui.form.Button("List Recipes");
with(listRecipesButton){
setLeft(10);
setTop(230);
setWidth(70);
}
rootDoc.add(listRecipesButton);
});
Если мы теперь откроем проект в браузере то на странице /recipes/ мы увидим следующее:

Мило, не правда ли? :) Старые добрые кнопочки.
Однако, одних только кнопочек недостаточно. Нам необходимо сделать так, чтобы эти кнопочки взаимодействовали с серверной стороной. Для начала привяжем получение списка рецептов. Для этого модифицируем application.js:
qx.core.Init.getInstance().defineMain(function() {
// Получаем основной документ
var rootDoc = qx.ui.core.ClientDocument.getInstance();
// Добавляем объект для связи с сервером
var recipeService = new qx.io.remote.RemoteRequest(”/recipes/list”, “GET”);
with(recipeService){
// Говорим серверу что данные нам нужны в XML
setRequestHeader(”Accept”, “text/xml”);
// Вешаем событие, которое вызывается при успешном завершении запроса
addEventListener(”completed”,
function(e){
// Преобразуем полученный XML в объекты с помощью qx.client.Builder и добавляем их в объект recipeList
new qx.client.Builder().build(recipeList, e.getData().getContent());
}
);
}
// Вызов удаленного сервиса
function loadRecipes(){
recipeService.send();
}
// Создаем заголовок формы и добавляем его в основной документ
var recipeLabel = new qx.ui.basic.Label(”Recipes”, “R”);
with(recipeLabel){
setLeft(10);
setTop(5);
}
rootDoc.add(recipeLabel);
// Создаем элемент “список”, в который мы будем принимать данные с сервера
var recipeList = new qx.ui.form.List();
with(recipeList){
setLeft(10);
setTop(20);
setWidth(400);
setHeight(200);
}
rootDoc.add(recipeList);
// Создаем кнопку для отправки запроса на сервер
var listRecipesButton = new qx.ui.form.Button(”List Recipes”);
with(listRecipesButton){
setLeft(10);
setTop(230);
setWidth(70);
// Вешаем событие, которое вызывается по нажатию кнопки
addEventListener(”execute”, loadRecipes);
}
rootDoc.add(listRecipesButton);
});
Теперь по нажатию на кнопку List Recipes будет отправляться запрос на сервер. Однако, со стороны сервера должен прийти адекватный ответ. Для этого модифицируем контроллер RecipesController, а именно метод list:
class RecipesController < ApplicationController
scaffold :recipe
def index
end
def list
@recipe_pages, @recipes = paginate :recipes
respond_to do |wants|
wants.xml{render :partial => “list”}
wants.html{render_scaffold}
end
end
end
Теперь если браузер запросит результат в виде HTML, то скрипт выдаст стандартный scaffold. Если же потребует XML - будет рендериться некий partial, который мы задаем в файле app/views/recipes/_list.rxml:
xml.tag!("qx.client.builder.Container"){
@recipes.each do |recipe|
xml.tag!("qx.ui.form.ListItem", :label => recipe.title, :value => recipe.id)
end
}
Вот вроде и всё. Открываем страницу /recipes/, жмем кнопку - и, в идеале, мы должны получить те данные, которые вбивали в самом начале:

Если же мы в самом начале забыли ввести данные или хотим чтобы данных было побольше - открываем страницу /recipes/list/ и вводим их через обычный scaffold :) Вуаля!
Итог
В общем и в целом, мне нравится работать с QooxDoo. Конечно, есть ряд существенных минусов, о которых тоже нельзя умолчать:
* Документация в большинстве своем содержит только перечисление свойств, методов и их параметров. Полноценно задокументированы лишь некоторые классы и методы. Однако, при наличии некоторого опыта, разобраться все же можно, благо названия все говорящие, да и примеры использования классов тоже есть (папка demo/test)
* Не самая высокая скорость работы библиотеки. А что вы хотели от почти метра JavaScript’овых кодов? Для такого объема - более чем прилично. Тем более что, насколько я понимаю, можно делать custom-билды (но об этом как-нибудь отдельно)
* Отсутствие средств разработки (IDE). С этим пока что ничего не поделаешь.
Приятного вам кода.