Я хочу забыть про JavaScript!

Опубликовано 09.11.2008

Желание выкинуть из головы это слово всплывает во мне всякий раз когда я хочу сделать в своем приложении нечто более сложное, чем показать или скрыть элемент. Синтаксис JavaScript меня просто убивает. Когда я пишу очередную функцию - меня терзают муки совести. Каждый раз, когда я хочу создать класс объектов, я мучаюсь приступами мигрени. Я не выношу этот язык!

Как вы думаете, к чему я это вам рассказываю? К тому, что есть свет в конце тоннеля!

Если вы еще не знаете про Red - самое время познакомиться. Это мощный инструмент, который компилирует код, написанный на Ruby, в обычный JavaScript, воспринимаемый браузером. После просмотра видео у меня аж руки зачесались переделать существенную часть клиентских скриптов:

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

Специализация рулит

Опубликовано 12.04.2007

В процессе изучения Qooxdoo 0.7 alpha2 и первых пробах новой системы построения классов, я пришел к, казалось бы, тривиальному, но тем не менее важному выводу. Важному для меня, в первую очередь, не как для программиста, а как для проектировщика и руководителя проектов. А вывод этот такой: при создании сложных клиент-серверных приложений (и, в частности, AJAX-приложений) нужно четко разделять обязанности по проектированию серверной и клиентской частей проекта. Вплоть до делегирования этих обязанностей различным разработчикам.

Qooxdoo 0.7 alpha2

Опубликовано 06.04.2007

Не мастак я придумывать красивые заголовки, но в этом случае придумал бы с удовольствием. Потому как опять про Qooxdoo.

Я, наверное, потихоньку влюбляюсь в этот продукт и этих ребят. Первый раз их увидел когда еще писал на PHP (да, я раньше был извращенцем, но я исправился). Тогда я повосхищался их кнопочками и менюшечками, показал знакомым программерам... В общем знакомство состоялось. Второй раз я за них взялся уже более-менее всерьез, скачал дистрибутив, поковырялся и написал мини-обзор о том как состыковать Qooxdoo и Ruby on Rails (его даже проанонсировали в официальном блоге Qooxdoo). Однако, при попытке создать более-менее рабочее приложение, я наткнулся на весьма неприятный баг с отправкой POST-форм (я об этом писал в старой версии журнала, которая лежит где-то на Majordomo). И вот, наконец, я в третий раз прихожу к ним - и как раз к моменту выхода Qooxdoo 0.7 alpha2.

Здесь, наверное, стоит завершить лирику и перейти к сути. Приближающаяся версия 0.7 несет нам ряд нововведений, которые я бы назвал знаковыми. Самое знаковое - это переработка механизма ООП и работы с классами. Прямо скажем, это нечто. При работе с Qooxdoo версии 0.6 меня сильно смутила некоторая громоздкость структур и нетривиальность работы с классами. Т.к. опыт работы с JavaScript у меня на тот момент был практически нулевой, вьехать в систему прототипов было ой-ой-ой как непросто. Собственно, этим не только Qooxdoo грешит. Я, например, до сих пор с трудом въезжаю в устройство классов Prototype, хотя пользуюсь им можно сказать каждый день.

В новой версии разработчики Qooxdoo планируют достаточно сильно упростить работу с классами. Предполагается что структура класса будет выглядеть примерно так:

qx.Class.define("my.cool.Class", {                           
  /* Наследование от класса */
  extend : my.great.SuperClass,           

  /* Интерфейсы а-ля Java*/                  
  implement : [my.cool.IInterface, my.other.cool.IInterface],

  /* Миксины а-ля Ruby */
  include : [my.cool.MMixin, my.other.cool.MMixin],          

  /* Конструктор */
  construct : function() {                                   
    this.base(arguments, x);                                 
    ...
  },        

  /* Деструктор */                                               
  destruct : function() {                                    
    ...
  },     

  /* Статичные методы класса */                                                    
  statics :                                                  
  {                                                          
    PI : 3.141,                                              
    bar : function() {}                                      
  },    

  /* Методы экземпляра класса */                                                     
  members:                                                   
  {                                                          
    foo : VALUE,                                             
    circumference : function(radius) {                       
      /* Вызов статичного метода класса */                   
      return 2 * this.self(arguments).PI * radius;           
    },                                                       

    bar : function(x) {                                      
      /* Вызов метода родительского класса */                
      this.base(arguments, x);                               
    },                                                       

    _protectedMember: function(x) { ... },                   

    __privateMember: function(x) { ... },                     

    browserSpecific: qx.Variant.select("qx.client",          
    {                                                        
      "mshtml|opera": function() {                           
         // Internet Explorer or Opera                       
      },                                                     
      "default": function() {                                
         // All other browsers                               
      }                                                      
    })
  },     

  /* События */                                                    
  events :                                                   
  {                                                          
    /*  Вызывается при кликепо объекту */                   
    "click": "qx.event.type.MouseEvent"
  }                                                          
});

Это сборная солянка из сравнения возможностей ООП в Qooxdoo 0.6 и 0.7. Мило, не правда ли? Вполне себе читаемо и поддается осмыслению без выворачивания мозгов наизнанку. Прямо как в номральных языках программирования. Особенно две примечательных вещи - это интерфейсы и миксины. Интерфейсы, насколько я понимаю, предполагаются по аналогии с Java (однако зачем они нужны - для меня пока загадка, я в джаве не силен). Миксины - как попытка затащить в JavaScript частичку мощи Ruby, а именно разработку абстрактного функционала, который может быть подключен к любому классу. Едва взглянув на эту задумку, я готов преклонить голову перед разработчиками за смелость и заботу о прогрессивном человечестве.

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

QooxDoo on Rails

Опубликовано 04.01.2007

Сегодня я постараюсь показать, как можно использовать возможности недавно вышедшей библиотеки 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/001createrecipes.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(&#8220;Recipes&#8221;, &#8220;R&#8221;);
  with(recipeLabel){
    setLeft(10);
    setTop(5);
  }
  rootDoc.add(recipeLabel);

  // Создаем элемент &#8220;список&#8221;, в который мы будем принимать данные с сервера
  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(&#8220;List Recipes&#8221;);
  with(listRecipesButton){
    setLeft(10);
    setTop(230);
    setWidth(70);
  }
  rootDoc.add(listRecipesButton);

});

Если мы теперь откроем проект в браузере то на странице /recipes/ мы увидим следующее:

Qooxdoo Interface

Мило, не правда ли? :) Старые добрые кнопочки.

Однако, одних только кнопочек недостаточно. Нам необходимо сделать так, чтобы эти кнопочки взаимодействовали с серверной стороной. Для начала привяжем получение списка рецептов. Для этого модифицируем 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!(&#8220;qx.client.builder.Container&#8221;){
  @recipes.each do |recipe|
    xml.tag!(&#8220;qx.ui.form.ListItem&#8221;, :label => recipe.title, :value => recipe.id)
  end
}

Вот вроде и всё. Открываем страницу /recipes/, жмем кнопку - и, в идеале, мы должны получить те данные, которые вбивали в самом начале:

Если же мы в самом начале забыли ввести данные или хотим чтобы данных было побольше - открываем страницу /recipes/list/ и вводим их через обычный scaffold :) Вуаля!

Итог

В общем и в целом, мне нравится работать с QooxDoo. Конечно, есть ряд существенных минусов, о которых тоже нельзя умолчать:

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

Приятного вам кода.