8 мар. 2011 г.

Знакомство с Ext JS

Если вы только начинаете использовать Ext или хотите узнать побольше о этой библиотеке – значит вы попали туда, куда нужно. В этом руководстве мы рассмотрим основные понятия Ext и создадим работающую динамическую страницу. Предполагается, что читатель уже знаком с основами Javascript и имеет некоторый опыт работы с этим языком программирования, а также знает основы HTML и DOM (объектной модели документа).



Скачиваем Ext


Для скачайте самую последнюю версию Ext, которую всегда можно найти по адресу http://extjs.com/download.

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


Начинаем работу!


Для начала давайте рассмотрим несколько наиболее типовых задач, выполняемых при помощи Javascript и то, как они решаются при помощи Ext. Сразу скачайте архив с примером приложения, которое мы создаем в этом руководстве – IntroToExt.zip. Архив содержит 3 файла: ExtStart.html, ExtStart.js и ExtStart.css. Распакуйте эти файлы в подпапку в папке куда у вас установлен Ext (например, если вы установили Ext в “C:\code\Ext\v1.0,” создайте в папке “v1.0″ подпапку “tutorial”). Откройте ExtStart.html в браузере, два раза кликнув по нему. Появится окошко с сообщением, что все установлено и настроено правильно. Если вместо окошка с подтверждением появится сообщение об ошибке, следуйте инструкциям на открывшейся странице, чтобы исправить ее.


Теперь, откройте файл ExtStart.js в вашем любимом редакторе. Вот что вы там увидите:
Ext.onReady(function() {
        alert("Congratulations!  You have Ext configured correctly!");
});

Ext.onReady будет самым первым методом, который вы будете использовать на своих страницах. Этот метод автоматически вызывается, когда полностью загружена в память DOM страницы гарантируя, что все элементы к которым вы можете захотеть обратиться будут уже загружены в память к моменту запуска скрипта. Теперь вы можете удалить строку с командой alert(), так-как далее на ее место мы добавим код, делающий что-нибудь полезное.


Объект Element: сердце Ext

Практически все задачи, что вы решаете при помощи Javascript требуют обращения к отдельным элементам страницы, чтобы иметь возможность что-то с ними сделать. Обычно в Javascript отдельный DOM-узел выбирался по id следующим образом:
var myDiv = document.getElementById('myDiv');

Такой подход неплохо работает - проблема в том, что возвращаемый объект (узел DOM) не очень удобен в работе. Чтобы сделать с ним что-то полезное приходится писать кучу дополнительного кода. Кроме того, приходится самостоятельно обрабатывать различия между разными браузерами, что довольно утомительно.

И тут на помощь приходит объект Ext.Element. Element является «сердцем» Ext, и большинство задач, которые вы будете решать предполагает работу с Element-ами. Весь Ext построен на базе Element, и если у вас есть время на то, чтобы как следует разобраться только с одним классом в Ext – пусть это будет класс Element.


Чтобы получить Ext Elemet по его id используйте следующий код (в странице ExtStart.html есть div с id «myDiv», так-что давайте добавим в ExtStart.js следующий код):
Ext.onReady(function() {
 var myDiv = Ext.get('myDiv');
});

Вернемся к объекту Element – чем-же он нам полезен?


  • Element предоставляет обертки для большинства DOM методов и свойств, которые вам могут понадобиться в работе. Это дает нам удобный, унифицированный и кросс-браузерный DOM-интерфейс (кроме того вы всегда можете обратиться к DOM-элементу напрямую, используя Element.dom)
  • Метод Element.get() использует кеширование, поэтому множественные обращения к одному и тому-же элементу происходят очень быстро
  • Для наиболее часто используемые действия, производимых над DOM-элементами у Element созданы простые кросс-браузерные методы (добавление/удаление классов CSS, добавление/удаление обработчиков событий, позиционирование, изменение размера, анимация, перетаскивание и т.д.)

Это значит, что множество полезных вещей делаются просто и с минимумом кодирования. Вот несколько простых примеров (полный список вы найдете в документации по API Element). Попробуйте подобавлять эти примеры в ExtStart.js после строки, в которой мы создали переменную myDiv:
myDiv.highlight();      // Заливка элемента станет желтой, а потом постепенно

                                 вернется к первоначальному цвету
myDiv.addClass('red');  // Добавляем CSS-класс (предопределенный в ExtStart.css)
myDiv.center();         // Ставим элемент по центру страницы
myDiv.setOpacity(.25);  // Делаем элемент полупрозрачным

Выборка узлов DOM



В некоторых случаях выбирать узлы DOM по id бывает неудобно или невозможно. Может быть id у элемента отсутствует или вы его не знаете, или вам надо выбрать сразу несколько элементов. Иногда вам может потребоваться выбрать элементы не по id, а на основании какого-то другого признака, например атрибута или класса CSS. Для этих задач в Ext существует библиотека DomQuery для выборок DOM-узлов.

DomQuery можно использовать отдельно, но обычно она используется вместе с Ext. Вы будете использовать ее для выборки элементов чтобы можно было работать с ними как с объектами Element. К счастью, объект Element содержит метод Element.select() для выборки при помощи библиотеки DomQuery. Например в файле ExtStart.html есть несколько параграфов (тегов <p>) у которых нет id. Если вам надо выбрать все параграфы и выполнить со всеми ними некоторые действия, вы можете сделать что-то вроде этого:
// Подсвечивает все параграфы
  Ext.select('p').highlight();    

Этот пример показывает очень удобную особенность Element.select – он возвращает CompositeElement, который предоставляет доступ ко всем выбранным Element-ам как к единому объекту Element. Это позволяет удобно работать с выбранными элементами без необходимости обращения к каждому из них в цикле.

Библиотека DomQuery поддерживает массу способов выборки, включая большинство селекторов из стандарта W3C CSS3 DOM, основные Xpath запросы, атрибуты HTML и много другого. Детальное описание вы можете посмотреть в API-документации DomQuery.


Реакция на события


Пока весь код в примерах мы помещали внутрь функции onReady, и значит код исполнялся сразу после загрузки страницы. Это заметно уменьшает нашу возможность влиять на события – зачастую нужно, чтобы код выполнялся в результате действий пользователя, или наступления какого-то события. Для решения этой задачи нужно создать обработчики событий, которые будут вызывать некие функции при наступлении этих событий.

Вот простой пример. Откройте файл ExtStart.js и поместите в него следующий код:
Ext.onReady(function() {
 Ext.get('myButton').on('click', function(){
  alert("You clicked the button");
 });
});


Этот код будет исполнен после загрузки страницы, но есть одна особенность. Хотя функция, вызывающая alert() и определена, она будет вызвана только после нажатия на кнопку, т.к. Она назначена обработчиком событию «click» для кнопки. Если перевести этот код на русский, он будет звучать следующим образом: «Взять элемент с id ‘myButton’ и назначить для него функцию, которая будет вызываться каждый раз при клике на этот элемент».

Неудивительно, что Element.select позволяет делать то-же с целой группой элементов. Например, если мы хотим чтобы сообщение показывалось при клике на любом параграфе, мы делаем следующее:
Ext.onReady(function() {
 Ext.select('p').on('click', function() {
  alert("You clicked a paragraph");
 });
});


В этих примерах мы создавали функцию-обработчик сразу, без присвоения ей имени функции. Такие функции называются «анонимными функциями», т.к. у них отсутствует имя функции. Помимо этого вы можете назначить обработчиком уже имеющуюся, обладающую именем, функцию. Это полезно, когда вы хотите использовать одну и ту-же функцию для обработки нескольких разных событий. Этот пример делает то-же самое, что и предыдущий:
Ext.onReady(function() {
 var paragraphClicked = function() {
  alert("You clicked a paragraph");
 }
 Ext.select('p').on('click', paragraphClicked);
});


Пока мы исполняли в обработчике простые действия, но как нам узнать, какой конкретно элемент вызвал событие? На самом деле это очень просто – метод Element.on принимает три очень полезных параметра для обработчика событий (здесь мы рассмотрим только первый из них, об остальных вы можете узнать из документации). В предыдущих примерах мы просто игнорировали эти дополнительные параметры, но немного исправив код мы получим дополнительную функциональность. Первый, и наиболее важный параметр – это само произошедшее событие. На самом деле это объект Event, кроссбраузерный и предоставляющий больше информации чем стандартный. Например узел DOM на котором произошло событие может быть получен путем небольшого изменения кода:
Ext.onReady(function() {
 var paragraphClicked = function(e) {
  Ext.get(e.target).highlight();
 }
 Ext.select('p').on('click', paragraphClicked);
});


Обратите внимание, что target – это DOM-узел, поэтому сначала мы должны получить соответствующий объект Element и уже на нем производить действия. В нашем случае мы по клику подсвечиваем параграф.

Использование виджетов


В дополнение к основным библиотекам, рассмотренным выше, в Ext содержит один из богатейших на сегодняшний день набор виджетов и компонентов интерфейса. Мы не сможем рассмотреть их все здесь, поэтому давайте остановимся на нескольких наиболее используемых.

Окно сообщения


Вместо того, чтобы создавать диалог с поднадоевшей надписью «Hello world», поступим хитрее. В прошлом примере мы уже написали код, подсвечивающий параграф, по клику на нем. Давайте сделаем так, чтобы текст параграфа по которому кликнули выводился в окне сообщения. Замените в функции paragraphClicked строку:
Ext.get(e.target).highlight();


на:
var paragraph = Ext.get(e.target);
 paragraph.highlight();
        Ext.MessageBox.show({
  title: 'Paragraph Clicked',
  msg: paragraph.dom.innerHTML,
  width:400,
  buttons: Ext.MessageBox.OK,
  animEl: paragraph
 });

Здесь мы использовали несколько новых вещей, давайте рассмотрим их поподробнее. В первой строке мы создаем локальную переменную paragraph которая содержит ссылку на Element, представляющий DOM-узел по которому кликнул пользователь (в этом примере это всегда параграф, так как мы связали обработчик событий только с тегами <p>). Зачем мы создали переменную? Нам понадобится Element чтобы подсветить его, а потом мы используем его-же в качестве одного из параметров окна сообщения. Вообще вызывать одну и ту-же функцию несколько раз чтобы получить одно и то-же значение или ссылку на объект непрактично и считается дурным тоном. Поэтому мы как хорошие ООП-разработчики используем локальную переменную.


Теперь перейдем к самому вызову объекта MessageBox, в котором есть еще несколько новых для нас понятий. На первый взгляд это выглядит как простая передача списка параметров методу, но если присмотреться, сам синтаксис довольно необычен. В данном случае в MessageBox.show() передается только один параметр: объект (object literal), содержащий набор свойств и их значений. В Javascript это динамический объект, создаваемый путем использования фигурных скобок { и } и списка свойство-значение между ними в формате [название свойства]:[значение свойства]. Такая запись активно используется в Ext, поэтому хорошо запомните ее.

Зачем использовать объект? Прежде всего из-за гибкости такого подхода. Объекту можно всегда добавить или убрать свойства, их можно писать в произвольном порядке в отличии параметров метода, порядок и тип которых не должен меняться. Это сильно упрощает жизнь разработчику, особенно при работе с методами, у которых масса необязательных параметров (как в случае с MessageBox.show). Например, представим что метод foo.action принимает четыре параметра, а мы хотим передать только один из них. В этом случае вам может понадобиться написать что-то вроде foo.action(null, null, null, ‘hello’). Однако,если метод принимает объект, этот же код превратится в foo.action({ param4: ‘hello’ }). Гораздо проще в использовании и значительно надежнее.

Гриды


Грид – это один из самых популярных Ext-виджетов, и зачастую люди начинают именно с него. Давайте посмотрим как нам построить простейший грид. Замените весь имеющийся в код ExtStart.js на:
Ext.onReady(function() {
 var myData = [
  ['Apple',29.89,0.24,0.81,'9/1 12:00am'],
  ['Ext',83.81,0.28,0.34,'9/12 12:00am'],
  ['Google',71.72,0.02,0.03,'10/1 12:00am'],
  ['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
  ['Yahoo!',29.01,0.42,1.47,'5/22 12:00am']
 ];
 var ds = new Ext.data.Store({
  proxy: new Ext.data.MemoryProxy(myData),
  reader: new Ext.data.ArrayReader({id: 0}, [
   {name: 'company'},
   {name: 'price', type: 'float'},
   {name: 'change', type: 'float'},
   {name: 'pctChange', type: 'float'},
   {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
  ])
 });
 ds.load();

var colModel = new Ext.grid.ColumnModel([
  {header: "Company", width: 120, sortable: true, dataIndex: 'company'},
  {header: "Price", width: 90, sortable: true, dataIndex: 'price'},
  {header: "Change", width: 90, sortable: true, dataIndex: 'change'},
  {header: "% Change", width: 90, sortable: true, dataIndex: 'pctChange'},
  {header: "Last Updated", width: 120, sortable: true,
   renderer: Ext.util.Format.dateRenderer('m/d/Y'),
                        dataIndex: 'lastChange'}
 ]);

var grid = new Ext.grid.Grid('grid-example', {ds: ds, cm: colModel});
 grid.render();
 grid.getSelectionModel().selectFirstRow();
});

Выглядит сложновато, но на самом деле здесь всего семь строчек кода! В первой строке создается массив данных, которые будут отображаться в гриде. В реальных проектах вы скорее всего будете брать эти данные динамически, например из базы данных или веб-сервиса. Следующим шагом мы инициализируем хранилище данных, которое отвечает за чтение и форматирование данных. Потом мы создаем схему столбцов, которая позволяет сконфигурировать каждую из колонок грида. И наконец мы создаем сам виджет, передавая ему хранилище данных и схему столбцов, отображаем ее и выделяем первую строчку. Ну как, ничего сложного?

Конечно, сейчас вы вероятно не полностью разобрались в приведенном коде (например, что это за хреновина – MemoryProxy?). Цель этого примера – показать как можно быстро и просто создать сложный элемент интерфейса с богатыми возможностями всего несколькими строками кода. Детали вы можете узнать прочитав и просмотрев другие описания или документацию по API гридов.

И еще много всего…



На самом деле к настоящему моменту мы видели только верхушку айсберга. В Ext есть множество других полезных компонентов интерфейса и виджетов – автоматическая разбивка страницы, вкладки, меню, панели инструментов, диалоги, «деревья» и многие другие. Смотрите раздел с примерами в документации чтобы познакомиться с полным списком доступных виджетов.

Использование Ajax


Теперь, когда мы создали страницу, и поработали с ней при помощи Javascript, пора разобраться с тем, как получать данные с удаленного сервера, что обычно означает загрузку и сохранение информации в базу данных. Подход, при котором такое взаимодействие происходит асинхронно и при помощи Javascript обычно называют Ajax, и в Ext встроена прекрасная поддержка этой технологии. Например, типичная задача заключается в обеспечении взаимодействия с пользователем – отправки введенных данных на сервер и обновлении какого-то элемента интерфейса в ответ на его действия. Вот пример простой HTML-формы с одним полем для ввода данных и кнопкой, и div который мы используем для отображения ответа сервера (Обратите внимание – вы можете добавить этот html-код в ExtStart.html, но вам нужен веб-сервер для того, чтобы разместить на нем серверную часть скрипта):
<div id="msg" style="visibility: hidden"</div

Name: <input type="text" id="name" /><br />
<input type="button" id="okButton" value="OK" />


Теперь давайте добавим Javascript-программу, которая будет брать введенные данные и отправлять их на сервер (замените весь код в ExtStart.js на следующий):
Ext.onReady(function(){
 Ext.get('okButton').on('click', function(){
  var msg = Ext.get("msg");
  msg.load({
   url: [server url], // <-- замените на ваш url

   params: "name=" + Ext.get('name').dom.value,
   text: "Updating..."
  });
  msg.show();
 });
}); 

Структура кода вам должна быть уже знакома. Мы создаем объект класса Element из кнопки okButton и «подвешиваем» анонимную функцию как обработчик на клик по этой кнопке. Внутри обработчика мы используем специальный класс Ext – UpdateManager, который значительно упрощает задачу отправки Ajax запроса на сервер, получения ответа и обновления элемента. Можно использовать UpdateManager напрямую или, как в этом примере вызывая соответствующий метод Element.load того Element-а который мы хотим обновить (в данном случае это div с id ‘msg’). При использовании Element.load ответ пришедший от сервера автоматически заменяет innerHTML элемента. Просто передадим ему URL программы на сервере, которая обработает наш запрос, параметры, передаваемые этому процессу (в нашем случае – это значение, введенное в поле ‘name’) и текст, который будет отображаться как innerHTML элемента, пока запрос обрабатывается. И наконец делаем div ‘msg’ видимым (т.к. он по-умолчанию скрыт) и готово! Конечно, UpdateManager как и многие другие компоненты Ext обладает гораздо большим числом возможностей и может по-разному работать с Ajax-запросами, но цель данного примера – показать как можно быстро и просто послать и обработать Ajax-запрос.

Последняя часть нашего Ajax-приложения – это программа на веб-сервере, которая обрабатывает полученный запрос и возвращает ответ. Это может быть страница со скриптом, сервлет, HTTP-обработчик, веб-сервис или просто CGI или Perl-скрипт – все что угодно, что может обрабатывать HTTP-запросы на сервере. К сожалению из-за большого числа вариантов нет возможности привести какой-то один «стандартный» пример. Вот несколько примеров на широко используемых языках, которые помогут вам понять принцип (код попросту выводит содержимое поля ‘name’, полученное им от клиента добавляя в начало строку ‘From Server: ‘ и она отображается внутри div-а ‘msg’):


PHP

<?php if(isset($_POST['name'])) {
  echo 'From Server: '.$_POST['name'];
 }
?>


ASP.Net

protected void Page_Load(object sender, EventArgs e)
{
 if (Request.Form["name"] != null)
 {
  Response.Write("From Server: " + Request.Form["name"]);
  Response.End();
 }
}


Cold Fusion

<cfif StructKeyExists(form, "name")>
 <cfoutput>From Server: #form.name#</cfoutput>

</cfif>

В реальной жизни работа с Ajax включает написание кода, который читает и обрабатывает данные, полученные от сервера. Есть несколько самых распространенных форматов обмена данными, такие как JSON и XML. Также существует множество серверных библиотек, которые помогут проще работать с Ajax-запросами. Все они совместимы с Ext, т.к. Ext не предъявляет никаких специфических требований к северным библиотекам. Ext все равно, что происходит на сервере, если результат обработки отдается в корректном формате!