Полный цикл разработки Ajax-приложений, Часть 2: Реализация уровней Ajax-клиента и сервера (исходники)

Сентил Натан, старший инженер-программист, IBM

В первой части данной серии статей вы узнали о важнейших функциональных возможностях таких технологий с открытым исходным кодом, как Firefox, Zend Core и MySQL. Вы познакомились с нетривиальным банковским сценарием, использующим все три уровня Ajax-приложения. Вы также установили сервер базы данных, промежуточный сервер и основанную на Eclipse IDE, необходимые для полного цикла разработки Ajax-приложения. В данной статье будут разработаны некоторые части банковского сценария. В частности, вы создадите базу данных, используя MySQL, а также познакомитесь с несколькими инструментальными программами MySQL, работающими из командной строки и используемыми для подключения, создания, определения и заполнения базы данных специфичными для приложения данными. Затем вы разработаете PHP-модуль промежуточного уровня для хранения бизнес-логики банка, использующий ODBC для подключения к базе данных MySQL. Наконец, вы разработаете портал банка (для предоставления простого пользовательского интерфейса в браузере), который пользователи могут использовать для взаимодействия с приложением, которое вскоре будет готово для выполнения на Zend Core.

Введение

Как говорилось в первой части данной серии статей, банковский сценарий концентрируется вокруг предоставления базовых сервисов обслуживания счета, которые выполняет кассир банка. Если вы еще этого не сделали, вам следует изучить сценарий, описанный в первой части. Пользовательские данные являются важной частью данного сценария. Для целей этого упражнения все пользовательские данные будут вноситься в таблицу базы данных за один прием. Затем данные могут быть извлечены и обновлены через ODBC-драйвер MySQL, поставляемый с Zend Core. После работы с данными мы переключимся на базовую логику банка, необходимую для реализации функций кассира. Вы разработаете модуль на PHP для предоставления базовой логики банка с необходимым доступом к базе данных при помощи ODBC. Главное преимущество использования Zend Core и PHP для этой логики - использование встроенной поддержки MySQL.

Закончив работу с базой данных и PHP-модулем, вы продолжите разрабатывать простой пользовательский интерфейс для выполнения кассиром банка четырех базовых операций. Как говорилось ранее, базовая логика заключена в PHP-модуле, доступ к которому осуществляется через "тонкий" клиент. Этот основанный на Web "тонкий" клиент будет сгенерирован в Ajax-стиле: XHTML, Cascading Style Sheets (CSS), JavaScript и XMLHttpRequest (XHR). Он обеспечит простой пользовательский интерфейс для выполнения кассиром банка базовых операций. Этот пользовательский интерфейс также продемонстрирует специфичный метод взаимодействия по сети клиентской логики с серверной логикой на PHP.

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

К концу данной части серии статей вы создадите базу данных, PHP-модуль для базовой логики банка и одностраничный пользовательский интерфейс для браузера. Все это части нашего банковского сценария.

База данных MySQL

Как вы узнали из первой части данной серии статей, MySQL - это база данных с открытыми исходными кодами. В нашем сценарии мы используем редакцию community server, являющуюся компактным сервером базы данных со многими полезными функциональными возможностями. Поскольку реализация банковского сценария основана на продуктах с открытыми исходными кодами, MySQL отлично подходит к PHP-серверу Zend Core. MySQL поддерживается в Zend Core. Существует несколько инструментальных программ для администрирования и программирования MySQL. В нашем сценарии для администрирования MySQL мы будем использовать только клиентскую программу командной строки. Мы создадим базу данных счетов банка, используя MySQL.

Создание и заполнение базы данных банка

В этом сценарии информация о счетах данного клиента хранится в следующем виде:

  • AccountHolderName
  • AccountNumber
  • CheckingBalance
  • StockName
  • StockQuantity
  • StockValue

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

Для создания базы данных и заполнения таблицы специфическими для приложения данными выполните следующие действия:

  1. Если среда Eclipse еще не запущена, запустите ее (c:\eclipse\eclipse.exe).
  2. Перейдите в перспективу PHP в Eclipse:
    1. Выберите Window->Open Perspective->Other->PHP и нажмите кнопку OK.
  3. В Eclipse выберите File->New->Project.
  4. Выберите General->Project и нажмите кнопку Next.
  5. Введите BankDB в поле project name.
  6. Нажмите кнопку Finish.
  7. Щелкните правой кнопкой мыши на проекте BankDB и выберите New->Other.
  8. Выберите General-> File и нажмите кнопку Next.
  9. В поле File name введите BankDB.sql и нажмите кнопку Finish.
  10. Введите или скопируйте содержимое исходного кода для BankDB.sql, приведенное в листинге 1.
  11. Сохраните и закройте файл.
  12. Для запуска клиента командной строки MySQL выберите Windows Start Menu->All Programs->MySQL->MySQL Server->MySQL Command Line Client.
  13. В окне командной строки MySQL введите пароль webtech и нажмите клавишу Enter.
  14. В приглашении mysql> введите source c:\eclipse\workspace\BankDB\BankDB.sql и нажмите клавишу Enter.
  15. Проверьте корректность выполнения предыдущей команды, убедившись в существовании базы данных BankDB. Вы можете сделать это, выполнив следующие команды:
    1. show databases;
    2. use bankdb;
    3. show tables;
    4. describe account;
  16. Чтобы закрыть клиент командной строки, введите exit.

Листинг 1. Содержимое файла BankDB.sql

                
			
-- Этот файл является частью статьи "Полный цикл Ajax-разработки" на 
-- IBM developerWorks. Этот файл содержит простой сценарий для
-- создания базы данных и заполнения его данными.
-- 
-- Дата последней модификации: 10 мая 2007
-- 
-- Для выполнения в MySQL приведенных ниже выражений проделайте следующее:
-- 1) Запустите клиент командной строки MySQL.
-- 2) Введите пароль администратора MySQL.
-- 3) Введите следующую строку, заменяя <YOUR_SQL_FILE_DIR> именем 
--    каталога, в котором хранится файл.
--    source <YOUR_SQL_FILE_DIR>\bankdb.sql

--
-- Структура таблицы 'BankDB'
--

DROP DATABASE BankDB;

CREATE DATABASE BankDB;

USE BankDB;

CREATE TABLE account (
   AccountHolderName VARCHAR(20) NOT NULL, 
   AccountNumber INTEGER NOT NULL,
   CheckingBalance DOUBLE NOT NULL,
   StockName VARCHAR(6),
   StockQuantity INTEGER,
   StockValue DOUBLE,
   PRIMARY KEY(AccountHolderName, AccountNumber)
);

--
-- Заполнение данными таблицы 'account'
--

insert into ACCOUNT values ('Frodo', 435245, 2344.45, 'GOOG', 100, 3453.32);
insert into ACCOUNT values ('Sam', 928462, 7583.32, 'CSCO', 200, 5323.43);
insert into ACCOUNT values ('Pippin', 234233, 3444.62, 'INTC', 300, 4213.76);
insert into ACCOUNT values ('Merry', 642445, 1005.32, 'MSFT', 250, 1353.32);
insert into ACCOUNT values ('Aragorn', 972321, 6424.24, 'HPQ', 525, 12043.94);
insert into ACCOUNT values ('Gandalf', 432134, 5392.23, 'IBM', 400, 10043.78);
insert into ACCOUNT values ('Legolas', 590134, 4313.82, 'DELL', 325, 5926.62);
		

Использование PHP для доступа к базе данных MySQL

Одной из популярных функциональных возможностей PHP является его простая классическая поддержка доступа к данным, хранящимся в базах данных разных производителей, в том числе MySQL. Он предоставляет эффективный и простой метод интегрирования бизнес-логики с базой данных. За последние несколько лет PHP-сообщество сделало несколько улучшений, например, PHP Data Objects (PDO), который предоставляет уровень абстракции и реализует общие функции API, независимые от используемого сервера базы данных. PHP-функции API базы данных реализованы также для процедурного и объектно-ориентированного стилей. В нашем сценарии мы будем использовать API базы данных, предназначенный для MySQL. PHP предлагает два способа доступа к MySQL:

  • MySQL
  • MySQL Improved

Обычное расширение для MySQL не поддерживает всю функциональность MySQL 4.1.0. К неподдерживаемым функциональным возможностям относятся хранимые процедуры, триггеры и виды (view). Расширение MySQL Improved (mysqli) является самым новым усовершенствованным способом доступа к этим функциям. Расширение для mysqli доступно в PHP 5.0 или старших версий. Однако mysqli не разрешен по умолчанию - вы должны сделать это при помощи программы администрирования Zend Core. В данном упражнении mysqli уже был разрешен в первой части данной серии статей при установке и настройке Zend Core.

Одной из замечательных функциональных возможностей API базы данных в PHP является то, что эти они могут отображать результаты операций с базой данных в структуры данных PHP множеством различных способов. Иными словами, в PHP существуют API базы данных, которые могут возвращать результаты в виде ассоциативного массива, в котором названия столбцов являются ключами. В другом случае результаты могут возвращаться в виде объекта класса PHP, в котором названия столбцов базы данных являются свойствами объекта. Это очень полезный способ представления результатов запросов к базе данных.

Строительные блоки функций PHP MySQLi

Как уже упоминалось, существует обширный массив функций mysqli, доступных в PHP. В данном разделе рассматриваются только те функции, которые используются при реализации сценария. В частности, рассматриваются такие функции базы данных как подключение, чтение, запись и отключение от базы данных. Всю дополнительную информацию вы найдете в официальной документации по PHP, ссылка на которую приведена в разделе "Ресурсы".

Перед выполнением каких-либо действий с базой данных вы должны установить соединение с сервером базы данных. В PHP для подключения к базе данных используется следующая функция:

$link = mysqli_connect("hostname", "user", "password", "dbname");

Эта функция делает попытку подключиться к указанной базе данных, расположенной на указанном хосте, с соответствующими полномочиями. Имеются также дополнительные параметры, которые принимает данная функция (см. документацию по PHP). При успешном подключении к базе данных функция возвращает объект connection, который необходим в последующих операция чтения и записи в базу данных. Если подключение выполнить не удалось, эта функция возвращает значение false. Если вы не хотите встраивать пароль к базе данных непосредственно в ваш код в текстовом виде, можно использовать функциональность MySQL password digest.

Дополняющей функцией к операции подключения к базе данных является функция закрытия базы данных:

$result = mysqli_close($link);

Эта функция пытается закрыть предварительно открытое подключение к базе данных. Она принимает в качестве параметра объект connection и возвращает значение true, если операция выполнилась успешно, и значение false в противном случае.

Рассмотрев функции управления подключением, давайте рассмотрим функции чтения и записи данных. Ниже приведен список полезных функций для операций чтения/записи:

  • (1) $result = mysqli_query($link, $queryStr);
  • (2) $numberOfRows = mysqli_num_rows($result);
  • (3) $row = mysqli_fetch_assoc($result);
  • (4) $row = mysqli_fetch_object($result);

Функция (1) выполняет SQL-запрос к базе данных. В качестве входных параметров она принимает объект connection и строку корректного SQL-запроса. Функция выполняет запрос и возвращает true при успешном выполнении и false в противном случае. Однако для SQL-запроса SELECT эта функция возвращает объект result.

Функция (2) получает количество строк в наборе, возвращаемом из SQL-запроса. В качестве параметра она принимает объект result.

Функция (3) получает строку результата в виде ассоциативного массива PHP. Этот массив содержит имена столбцов базы данных как ключи и значения полей базы данных как значения массива. Например, если запрос к базе данных возвратил значение столбца "capital", к результату можно обратиться так:

$stateCapital = $row['capital'];

Функция (4) получает строку результата в виде PHP-объекта. Этот объект содержит имена столбцов базы данных как свойства объекта и значения полей базы данных как значения свойства объекта. Например, если запрос к базе данных возвратил значение столбца "park", к результату можно обратиться путем разыменования значения объекта:

$nationalPark = $row->park;

Функции 3 и 4 предоставляют очень простой способ отобразить значения базы данных в программные переменные. Как упоминалось ранее, PHP также предлагает несколько других способов отображения значений базы данных.

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

  • $returnCode = mysqli_errno($link);
  • $errorMsg = mysqli_error($link);

Функция mysqli_errno возвращает код ошибки самого последнего выполненного вызова функции. Она принимает в качестве параметра объект connection и возвращает код для выполненной функции. Код 0 указывает на отсутствие ошибок. Функция mysqli_error возвращает строковое описание последней ошибки. Обе эти функции важны для выполнения полного цикла запроса к базе данных.

Реализация бизнес-логики банка в PHP-модуле

Выполните следующие действия для создания PHP-модуля и реализации бизнес-логики промежуточного уровня:

  1. Если это еще не сделано, перейдите в перспективу Eclipse PHP: выберите Window->Open Perspective->Other->PHP и нажмите кнопку OK.
  2. Выберите File->New->PHP Project:
    1. В поле Project name введите BankTeller.
    2. Нажмите копку Finish.
  3. В виде Project Explorer щелкните правой кнопкой мыши на проекте BankTeller и выберите New->PHP File:
    1. В поле File Name введите BankLogic.php и нажмите кнопку Finish.
  4. Замените содержимое этого файла, введя или скопировав исходный код из листинга 2.
    1. Выберите File->Save для сохранения файла.
  5. Просмотрите комментарии в этом файле для понимания кода или обратитесь к следующему разделу, в котором приведено общее описание логики кода.

Можно заметить, как просто выполняются операции с базой данных на PHP. В листинге 2 пароль объявляется в текстовом виде. Этот пароль используется только в тестовой базе данных, создаваемой в данной статье. В дальнейшем вы можете улучшить код, используя (вместо обычного текста) пароль в виде дайджеста (digest).

Листинг 2. Содержимое файла BankLogic.php

                

<?php
/*
============================================================
Проект: Полный цикл разработки Ajax-приложения

Назначение: Это пример сценария, используемого 
 в статье на IBM developerWorks.

Дата последней модификации: 11 мая 2007.

Этот PHP-модуль реализует базовую логику кассира банка,
необходимую для доступа к данным клиента в 
базе данных. Эти функции могут быть вызваны другими PHP-модулями,
имеющимися в сценарии Bank. Вся логика, включенная в операции
кассира банка, подразделяется на три следующие
базовые функции.

 a) Получить информацию о всех счетах
 b) Обработать транзакцию (депозит или дебет)
 c) Вычислить стоимость портфеля ценных бумаг

Здесь используются функции mysqli для прямого доступа к базе данных, 
в противоположность функциям PHP PDO. 
============================================================
*/
   // Эти переменные globals будут использоваться для хранения следующих значений:
   // 1) $link - для хранения объекта connection.
   // 2) $dbResult - для хранения объекта result, результата запроса к базе данных.
   // 3) $finalResult - это ассоциативный массив, в котором упакованы
   //    и возвращены вызывающей стороне результаты 
   //    конкретных операций кассира банка.
   global $link, $dbResult, $finalResult;

   /*
   ============================================================
   Функция: connect_to_db

   Дата последней модификации: 11 мая 2007.
	
   Эта функция выполняет подключение к базе данных BankDB MySQL.
   Если попытка подключения успешна, она сохраняет 
   объект connection в глобальной переменной и 
   возвращает значение true. 
   В противном случае возвращается значение false.
   ============================================================ 
   */
   function connect_to_db() {
      // Мы будем использовать следующие глобальные переменные.
      global $link, $dbResult, $finalResult;
		
      // Инициализация переменных.
      $link = null;
      $dbResult = null;
      $finalResult = null;
		
      // Выполнить подключение mysqli к локальной базе данных BankDB, используя 
      // соответствующие параметры доступа.
      $link = mysqli_connect("localhost", "root", "webtech", "bankdb");

      // Проверить, работает ли подключение к DB.
      if (mysqli_connect_errno()) {
         // Похоже, что есть какая-то проблема.
         // Получить номер MySQL-ошибки и строку, описывающую ошибку.
         $resultMsg = "DB connection error: " . mysqli_connect_errno() .
            " [" . mysqli_connect_error() . "]";
         // Сохранить результаты в ассоциативном массиве.
         // Установить результат операции как неудачный.
         $finalResult["ResultCode"] = 1;
         // Записать сообщение результата.
         $finalResult["ResultMsg"] = $resultMsg;
         // Возвратить значение false для индикации ошибки подключения к DB.
         return (false);
      } else {
         // Подключение DB успешно.
         return(true);		
      } // Конец if (mysqli_connect_errno())
   } // Конец функции connect_to_db

   /*
   ============================================================
   Функция: close_connection_to_db

   Дата последней модификации: 11 мая 2007.

   Эта функция закрывает выполненное ранее подключение к 
   базе данных BankDB.
   ============================================================ 
   */	
   function close_connection_to_db() {
      // Мы будем использовать следующие глобальные переменные.
      global $link, $dbResult;
	
      // Закрыть подключение, если имеется активный 
      // объект connection.
      if ($link != null) {
         if (($dbResult != null) && (is_object($dbResult))) {
            // Если DB result содержит объект запроса данных, 
			// освободить занимаемую им память.
            mysqli_free_result($dbResult);
            $dbResult = null;
         } // Конец if ($dbResult != null)
			
         // Пора закрыть DB connection.
         mysqli_close($link);
         // Установить переменную объекта connection в значение null.
         $link = null;			
      } // Конец if ($link != null)
   } // Конец функции close_connection_to_db

   /*
   ============================================================
   Функция: getAllAccountInformation
	
   Дата последней модификации: 11 мая 2007.
	
   Эта функция считывает информацию обо всех счетах, хранящихся в
   BankDB, и возвращает их в ассоциативном массиве.
   ============================================================ 
   */			
   function getAllAccountInformation() {
      // Мы будем использовать следующие глобальные переменные.
      global $link, $finalResult, $dbResult;
	
      // Выполнить подключение к BankDB.
      $result = connect_to_db();
	
      // Если подключение неудачно, возвратить управление.
      if ($result == false) {
         return ($finalResult);
      } // Конец if ($result == false)
		
      // SQL-запрос для получения всех строк таблицы account.
      $queryStr = "Select * from account";
      // Выполнить SQL-запрос.
      $dbResult = mysqli_query($link, $queryStr);
		
      // Обработать ошибки запроса, если они имеются.
      if (mysqli_errno($link)) {
         // Сначала закрыть подключение.
         close_connection_to_db();		
         // Записать сообщение об ошибке в окончательный ассоциативный массив.	
         $resultMsg = "DB read error: " . mysqli_errno($link) .
            " [" . mysqli_error($link) . "]";
         // Установить специфический для приложения код возврата FAILURE.
         $finalResult["ResultCode"] = 1;				
         $finalResult["ResultMsg"] = $resultMsg;
         // Возвратить окончательный результат.
         return ($finalResult);
      } // Конец if (mysqli_errno($link))
		
      // Получить количество запрошенных строк.
      $rowCount = mysqli_num_rows($dbResult);
	
      // Если база данных пуста, возвратить управление.
      if ($rowCount <= 0) {
         // Сначала закрыть подключение.
         close_connection_to_db();
         // Записать код возврата и возвращаемое сообщение.
         $finalResult["ResultCode"] = 1;				
         $finalResult["ResultMsg"] = "No accounts were found in BankDB.";
         // Возвратить окончательный результат.
         return ($finalResult);			
      } // Конец if ($rowCount <= 0)

      // Установить counter в значение 0.
      $cnt = 0;
      // Создать массив для хранения всех полей из каждой строки.
      $accountInfo = array();

      // Из каждой строки результата запроса извлечь поля данных.
      while($row = mysqli_fetch_assoc($dbResult)) {
         // Инициализировать в null.
         $data = null;
         // Создать ассоциативный массив "на лету" и сохранить 
         // конкретные поля данных, считанные из текущей строки базы данных.
         $data["AccountHolderName"] = $row["AccountHolderName"];
         $data["AccountNumber"] = $row["AccountNumber"];
         $data["CheckingBalance"] = doubleval($row["CheckingBalance"]);
         $data["StockName"] = $row["StockName"];
         $data["StockQuantity"] = intval($row["StockQuantity"]);
         $data["StockValue"] = doubleval($row["StockValue"]);
         // Теперь записать весь ассоциативный массив в обычный массив.
         $accountInfo[$cnt++] = $data;
      } // Конец while($row = mysqli_fetch_assoc($dbResult))
		
      // Мы завершили чтение информации обо всех счетах из базы данных.
      // Установить код возврата, как успешный.
      $finalResult["ResultCode"] = 0;
      // Установить сообщение результата, как успешное.
      $finalResult["ResultMsg"] = "ReadAllAccountsInfoFromDB successful";
      // Сделать весь массив, содержащий информацию обо всех счетах,
      // окончательным ассоциативным массивом результата.
      // $finalResult - это ассоциативный массив, содержащий 
      // обычный массив, который в свою очередь содержит несколько ассоциативных 
      // массивов.
      $finalResult["AccountInfo"] = $accountInfo;
      // Закрыть подключение.	
      close_connection_to_db();	
      // Возвратить окончательный массив результатов.
      return($finalResult);		
   } // Конец функции getAllAccountInformation

   /*
   ============================================================
   Функция: accountTransaction

   Дата последней модификации: 11 мая 2007.
	
   Эта функция выполняет транзакцию, выбранную кассиром банка.
   Функция принимает имя владельца счета, количество и
   тип транзакции в качестве параметров.
   Тип транзакции 1 - это депозит на счет.
   Тип транзакции 2 - это дебет.
   После выполнения транзакции возвращается состояние 
   счета до этой транзакции и состояние счета после транзакции.
   ============================================================ 
   */	
   function accountTransaction($accountHolderName, $amount, $transactionType) {
      // Мы будем использовать следующие глобальные переменные.
      global $link, $finalResult, $dbResult;
      // Выполнить подключение к базе данных.
      $result = connect_to_db();
		
      // Если подключение неуспешно, выполнить возврат.
      if ($result == false) {
         return ($finalResult);
      } // Конец if ($result == false)

      // SQL-запрос для чтения конкретных данных о счете 
      // данного владельца счета.
      $queryStr = "Select * from account where AccountHolderName='$accountHolderName'";
      // Выполнить SQL-запрос.
      $dbResult = mysqli_query($link, $queryStr);
	
      // Возникла ошибка?
      if (mysqli_errno($link)) {
         // Закрыть подключение.
         close_connection_to_db();
         // Записать сообщение об ошибке.
         $resultMsg = "DB read error: " . mysqli_errno($link) .
            " [" . mysqli_error($link) . "]";
         // Установить специфичный для приложения код возврата.
         $finalResult["ResultCode"] = 1;				
         // Записать сообщение результата.
         $finalResult["ResultMsg"] = $resultMsg;
         // Возвратить ассоциативный массив с результатом ошибки.
         return ($finalResult);
      } // Конец if (mysqli_errno($link))

      // Сколько строк было прочитано из DB?
      $rowCount = mysqli_num_rows($dbResult);
	
      // Если строк нет, возвратить управление.
      if ($rowCount <= 0) {
         // Закрыть подключение.
         close_connection_to_db();
         // Установить возвращаемое значение как неудачное.
         $finalResult["ResultCode"] = 1;				
         $finalResult["ResultMsg"] = "No accounts were found in BankDB.";
         // Возвратить окончательный результат.
         return ($finalResult);			
      } // Конец if ($rowCount <= 0)

      // Сохранить запись с текущим балансом.
      // Создать массив для хранения состояний до и после транзакции.
      $accountInfo = array();		
      // Извлечь результат запроса в виде ассоциативного массива.
      $row = mysqli_fetch_assoc($dbResult);		
      $data = null;
      // Записать значения полей из строки базы данных в 
      // новый ассоциативный массив.
      $data["AccountHolderName"] = $row["AccountHolderName"];
      $data["AccountNumber"] = $row["AccountNumber"];
      $data["PreviousCheckingBalance"] = doubleVal($row["CheckingBalance"]);
      $data["StockName"] = $row["StockName"];
      $data["StockQuantity"] = intval($row["StockQuantity"]);
      $data["StockValue"] = doubleVal($row["StockValue"]);
      // Записать состояние счета до транзакции в обычный массив.
      $accountInfo[0] = $data;

      // Инициализировать переменные.
      $newBalance = 0.0;
      // Поскольку мы выполняем транзакцию, меняющую 
      // баланс, необходимо потом обновить DB.
      $updateDB = true;

      if ($transactionType == 1) {
         // Это депозит.
         // Сумма депозита больше 0?
         if ($amount > 0) {
            $newBalance = doubleval($data["PreviousCheckingBalance"]) + $amount;
         } else {
            // Сумма депозита равна 0. Нет необходимости обновлять базу данных.
            $updateDB = false;
            $newBalance = doubleval($data["PreviousCheckingBalance"]);
         } // Конец if ($amount > 0)
      } else if ($transactionType == 2) {
         // Это дебет.
         // Проверить, что сумма дебета больше 0 и 
         // меньше имеющейся на счете.
         if (($amount > 0) &&
            ($amount < doubleval($data["PreviousCheckingBalance"]))) {
            $newBalance = doubleval($data["PreviousCheckingBalance"]) - $amount;	
         } else {
            // Сумма дебета либо 0, либо больше имеющейся на счете.
            $updateDB = false;
            $newBalance = doubleval($data["PreviousCheckingBalance"]);
         }
      } // Конец if ($transactionType == 1)
		
      // Транзакция, выполненная кассиром банка, завершилась.
      // Прекрасно. Возвратить результат.
      $finalResult["ResultCode"] = 0;
      $finalResult["ResultMsg"] = "AccountTransaction successful.";

      // Если мы изменили баланс счета операцией 
      // депозита или дебета, обновить базу данных.
      if ($updateDB == true) {
         // Подготовить выражение SQL Update.
         $updateStr = "update account set CheckingBalance=$newBalance " .
            "where AccountHolderName='$accountHolderName'";

         // Выполнить запрос на обновление.
         $dbResult = mysqli_query($link, $updateStr);

         // Есть ли ошибки?
         if (mysqli_errno($link)) {
            // Закрыть подключение к базе данных.
            close_connection_to_db();				
            // Подготовить возвращаемое сообщение об ошибке.
            $resultMsg = "DB write error: " . mysqli_errno($link) .
               " [" . mysqli_error($link) . "]";
            $finalResult["ResultCode"] = 1;
            $finalResult["ResultMsg"] = $resultMsg;
            // Возвратить сообщение об ошибке.
            return ($finalResult);
         }
					
         if ($dbResult == true) {
            // Все хорошо.
            // Подготовиться к возврату результата данной операции.
            $finalResult["ResultCode"] = 0;
            $finalResult["ResultMsg"] = 
               "New balance for $accountHolderName has been stored in DB.";
         } else {
            $finalResult["ResultCode"] = 1;
            $finalResult["ResultMsg"] = 
            "New balance for $accountHolderName could not be stored in DB.";
            // Возвратить окончательный результат с ошибкой.
            return($finalResult);
         } // Конец if ($dbResult == true)				
      } // Конец if ($updateDB == true)
		
      // Мы уже сохранили состояние счета до транзакции в
      // массиве в начале этой функции.
      // Сохранить вторую запись, которая будет содержать обновленный баланс.
      $data = null;
      $data["AccountHolderName"] = $row["AccountHolderName"];
      $data["TransactionAmount"] = $amount;
      $data["AccountNumber"] = $row["AccountNumber"];
      $data["NewCheckingBalance"] = $newBalance;
      $data["StockName"] = $row["StockName"];
      $data["StockQuantity"] = intval($row["StockQuantity"]);
      $data["StockValue"] = doubleval($row["StockValue"]);
      $accountInfo[1] = $data;		
      $finalResult["AccountInfo"] = $accountInfo;	
      // Закрыть подключение к DB.
      close_connection_to_db();	
      // Возвратить окончательный результат.
      return($finalResult);				
   } // Конец функции accountTransaction.

   /*
   ============================================================
   Функция: portfolioValue
	
   Дата последней модификации: 11 мая 2007.
	
   Эта функция сохраняет текущее значение стоимости портфеля ценных бумаг
   данного владельца счета в базе данных. Она принимает имя 
   владельца счета и текущую цену акции в качестве входных
   аргументов. Она считывает текущую позицию (в сценарии
   Bank каждый владелец владеет акциями только одной компании;
   это сделано для упрощения задачи.) и вычисляет
   текущую рыночную стоимость. Затем функция
   обновляет базу данных новым значением суммарной стоимости акций.
   ============================================================ 
   */		
   function portfolioValue($accountHolderName, $stockPrice) {
      // Мы будем использовать следующие глобальные переменные.
      global $link, $finalResult, $dbResult;

      // Подключиться к базе данных MySQL BankDB.
      $result = connect_to_db();
	
      // При ошибке подключения возвратить управление.
      if ($result == false) {
         return ($finalResult);
      } // Конец if ($result == false)

      // Подготовить строку запроса для получения информации о счете данного владельца.
      $queryStr = "Select * from account where AccountHolderName='$accountHolderName'";
      // Выполнить запрос MySQL.
      $dbResult = mysqli_query($link, $queryStr);
	
      // Есть ошибки?
      if (mysqli_errno($link)) {
         // Ошибка. Закрыть подключение к db.  
         close_connection_to_db();
         // Подготовить сообщение об ошибке для возврата.
         $resultMsg = "DB read error: " . mysqli_errno($link) .
            " [" . mysqli_error($link) . "]";
         $finalResult["ResultCode"] = 1;				
         $finalResult["ResultMsg"] = $resultMsg;
         // Возвратить ошибку.
         return ($finalResult);
      } // Конец if (mysqli_errno($link))

      // Были считаны какие-либо строки из DB? 
      $rowCount = mysqli_num_rows($dbResult);
	
      // Если соответствующих строк в DB нет, возвратить управление.
      if ($rowCount <= 0) {
         // Закрыть подключение к DB.
         close_connection_to_db();
         $finalResult["ResultCode"] = 1;				
         $finalResult["ResultMsg"] = "No accounts were found in BankDB.";
         // Возвратить окончательный результат.
         return ($finalResult);			
      } // Конец if ($rowCount <= 0)

      // Сохранить запись с текущим значением стоимости портфеля.
      // Выделить массив для хранения состояния счета 
      // до и после транзакции.
      $accountInfo = array();		
      // Извлечь результаты из базы данных в виде ассоциативного массива.
      $row = mysqli_fetch_assoc($dbResult);		
      // Сохранить поля данных строки в ассоциативный массив.
      $data = null;
      $data["AccountHolderName"] = $row["AccountHolderName"];
      $data["AccountNumber"] = $row["AccountNumber"];
      $data["CheckingBalance"] = doubleval($row["CheckingBalance"]);
      $data["StockName"] = $row["StockName"];
      $data["StockQuantity"] = intval($row["StockQuantity"]);
      $data["PreviousPortfolioValue"] = doubleval($row["StockValue"]);
      // Записать ассоциативный массив в обычный массив.
      $accountInfo[0] = $data;

      // Вычислить стоимость портфеля.
      $finalResult["ResultCode"] = 0;
      $finalResult["ResultMsg"] = "PortfolioValue successfully calculated.";
      $newPortfolioValue = intval($row["StockQuantity"]) * doubleval($stockPrice);
	
      // Поскольку стоимость портфеля изменилась, обновить ее в DB.
      $updateStr = "update account set StockValue=$newPortfolioValue " .
         "where AccountHolderName='$accountHolderName'";

      // Выполнить SQL-запрос.
      $dbResult = mysqli_query($link, $updateStr);

      // Есть ли ошибки DB?
      if (mysqli_errno($link)) {
         // Закрыть подключение к DB.
         close_connection_to_db();		
         // Подготовить сообщение об ошибке для возврата.		
         $resultMsg = "DB write error: " . mysqli_errno($link) .
            " [" . mysqli_error($link) . "]";
         $finalResult["ResultCode"] = 1;
         $finalResult["ResultMsg"] = $resultMsg;
         // Возвратить сообщение об ошибке.
         return ($finalResult);
      } // Конец if (mysqli_errno($link))
			
      if ($dbResult == true) {
         // Все нормально.
         // Подготовить для возврата сообщение об успешном выполнении.
         $finalResult["ResultCode"] = 0;
         $finalResult["ResultMsg"] = 
            "New stock value for $accountHolderName has been stored in DB.";
      } else {
         $finalResult["ResultCode"] = 1;
         $finalResult["ResultMsg"] = 
            "New stock value for $accountHolderName could not be stored in DB.";
         // Обновление неудачно. Поэтому возвратить ошибочный результат.
         return($finalResult);
      } // Конец else в if ($dbResult == true)				
		
      // Записать вторую запись, которая будет иметь обновленное значение стоимости 
      // портфеля. Мы уже сохранили состояние до транзакции в массиве.
      // Сохраним также состояние после транзакции.
      $data = null;
      $data["AccountHolderName"] = $row["AccountHolderName"];
      $data["AccountNumber"] = $row["AccountNumber"];
      $data["CheckingBalance"] = doubleval($row["CheckingBalance"]);
      $data["StockName"] = $row["StockName"];
      $data["CurrentStockPrice"] = doubleval($stockPrice);
      $data["StockQuantity"] = intval($row["StockQuantity"]);
      $data["NewPortfolioValue"] = $newPortfolioValue;
      // Записать ассоциативный массив в обычный массив.
      $accountInfo[1] = $data;		
      $finalResult["AccountInfo"] = $accountInfo;	
      // Закрыть подключение к DB и возвратить результат.
      close_connection_to_db();	
      return($finalResult);				
   } // Конец функции portfolioValue.
?>
		

Логика PHP-модуля BankLogic

PHP-файл, приведенный в листинге 2, содержит бизнес-логику для базовых операций кассира банка. В частности, к этим операциям относятся получение информации обо всех счетах, имеющихся в базе данных, выполнение транзакций для операций депозита и дебета, вычисление стоимости портфеля ценных бумаг для данного клиента. Все эти функции принимают некоторые входные параметры и возвращают ассоциативный массив, содержащий соответствующую информацию о счете. Основная часть логики работы этих функций состоит в подключении к базе данных BankDB MySQL и выполнении операций чтения или обновления. В начале листинга 2 определяются три PHP-переменные с глобальной областью видимости. Эти переменные хранят объект подключения к базе данных ($link), объект результатов SQL-запроса ($dbResult) и окончательный результат ($finalResult), возвращаемый процедуре, вызвавшей этот PHP-модуль. Этот PHP-модуль имеет две функции для подключения и отключения от базы данных MySQL. После подключения создается объект connection и сохраняется в переменной $link, которая доступна во всем этом PHP-файле, поскольку имеет глобальную область видимости. Аналогично, при выполнении операций чтения или обновления DB результаты SQL-запроса сохраняются в переменной $dbResult, которая тоже является глобальной. При необходимости закрытия подключения к базе данных используется другая вспомогательная функция (close_connection_to_db). Перед закрытием подключения к базе данных эта функция освобождает память, занимаемую переменной $dbResult.

Частью данного файла являются три базовые функции:

  • Функция getAllAccountInformation вызывается для извлечения данных обо всех счетах, имеющихся в базе данных. В коде выполняется ODBC-подключение к базе данных MySQL при помощи MySQLi extension API и извлекается информация обо всех счетах, хранящаяся в таблице account. После успешной операции чтения выполняется итерация по множеству результатов и выбирается информация о конкретном счете. Информация обо всех счетах записывается в ассоциативный PHP-массив, и этот массив возвращается программе, вызвавшей функцию.
  • Функция accountTransaction вызывается для выполнения операции депозита или дебета. В качестве входных параметров передаются имя владельца счета, сумма транзакции и тип транзакции для указания операции депозита или дебета. Выполняется ODBC-подключение к базе данных, и данные о текущем счете указанного владельца счета извлекаются из базы данных и записываются в ассоциативный массив. Это - состояние счета до транзакции. Затем выполняется необходимая транзакция (депозит или дебет). База данных обновляется с новым балансом. Это состояние счета после транзакции тоже записывается в другой ассоциативный массив. Оба состояния счета (до и после транзакции) записываются в обычный массив и возвращаются как окончательный результат программе, вызвавшей данную функцию. Также обратите внимание на то, что код выполняет транзакцию только в том случае, если указано корректное значение суммы транзакции.
  • Метод portfoliovalue вызывается для вычисления значения стоимости портфеля ценных бумаг для данного владельца счета. В качестве входных параметров передаются имя владельца счета и текущая рыночная стоимость ценных бумаг, которыми владеет данный клиент. Этот метод принимает состояние до транзакции и сохраняет его в ассоциативном массиве. Затем вычисляется новое значение стоимости портфеля ценных бумаг и обновляется соответствующее поле в базе данных Bank. В другой ассоциативный массив записывается также состояние счета после транзакции. Оба состояния счета (до и после транзакции) записываются в обычный массив и возвращаются как окончательный результат программе, вызвавшей данную функцию.

В исходном файле имеются комментарии, помогающие понять логику кода.

Одностраничный пользовательский интерфейс в стиле Ajax

Для пользовательского интерфейса кассира банка вы разработаете одностраничное приложение, работающее в браузере. Обратите внимание на то, что одностраничное приложение отличается от традиционного Web-приложения "Нажми и жди". В Web-приложениях "Нажми и жди" каждая страница извлекается с сервера путем загрузки всего содержимого каждый раз при переходе пользователя на новую страницу. Напротив, модель, используемая для выполняющихся в браузере одностраничных приложений, является полной противоположностью такого подхода. В ней все содержимое для представления (XHTML, CSS и JavaScript), необходимое приложению, извлекается с сервера только один раз. Такая одноразовая загрузка выполняется обычно в начале работы приложения, когда пользователь указывает в браузере URL приложения. Все необходимые приложению элементы пользовательского интерфейса определяются в XHTML-файле. Вся информация по оформлению (стили) для различных частей пользовательского интерфейса кодируется в CSS-файле. Для скрытия и показа нужных частей пользовательского интерфейса применяются специальные технические приемы Document Object Model (DOM), которые симулируют многостраничное поведение без извлечения страниц с сервера. JavaScript-код приложения, выполняющийся в браузере, может справиться с основной частью логики приложения. Используемая для проектирования приложения Ajax-технология делает приложение очень чувствительным к действиям пользователя. Она также снимает с сервера значительную часть нагрузки обычного генерирования страниц. В Ajax-приложениях взаимодействия браузер-сервер сводятся только к обмену специфическим для приложения данными, что оптимизирует ресурсы сервера при работе с большим количеством пользователей и приложений.

Кроме описанных преимуществ имеются и другие архитектурные вопросы, такие как защищенность данных, конфиденциальность данных и выделение соответствующей бизнес-логики, выполнение которой можно доверить браузеру клиента. Кроме того, существует необходимость восстановления после ошибок при прерывании HTTP-транзакции. Эти вопросы выходят за рамки данной статьи, все внимание в которой уделяется только реализации одностраничного приложения с простыми характеристиками. Также существует много Ajax-приемов, которые вы можете комбинировать для предоставления полнофункциональной графики в приложении. В данной статье вы рассмотрите только некоторые основные технические приемы для ознакомления с концепциями создания одностраничного приложения, выполняющегося в браузере.

Одним из приемов, используемых для скрытия и показа различных частей приложения, является применение HTML-тегов <DIV>, <SPAN> и <TABLE>. Простой обзор этих тегов приведен на рисунке 1. Тег <DIV> позволяет разделить HTML-документ на несколько отдельных секций. Помещение элементов управления конкретного экрана внутри тега <DIV> является первым шагом в разделении приложения на многие экраны. После этого при помощи JavaScript будет легко управлять видимостью экрана или элемента <DIV> на конкретном этапе работы приложения. Еще одним удобным тегом в данном упражнении является тег <SPAN>, который помогает применить стили к текстовым фрагментам приложения. В сценарии работы банка этот тег используется для создания меню. Также используется тег <TABLE> для управления схемой документа.

Рисунок 1. Теги <SPAN>, <DIV>, <TABLE>
>Рисунок 1. Теги , <DIV>, <TABLE>

Еще одним обычным способом добавления функциональности в выполняющиеся в браузере приложения является стилевое оформление на уровне отдельных элементов пользовательского интерфейса. Для управления внешним видом приложений, выполняющихся в браузере, широко используется CSS. CSS - это отдельный код со своим собственным синтаксисом. CSS легко настроить, поскольку для этого не нужны никакие подключаемые модули - все делается в текстовом файле путем определения правил. CSS-правило состоит из селектора (selector) и блока объявлений (declaration block). Селекторы начинают каждое правило, используя фигурную скобку. Блок объявлений окружен фигурными скобками и состоит из объявлений. Объявления - это пары свойство/значение, разделенные точкой с запятой. В листинге 3 приведен простой пример CSS-правила. Существуют разные типы селекторов CSS, что показано на рисунке 2.

 
Объяснение всех возможностей CSS-селекторов потребовало бы написания отдельной статьи. Можете потратить некоторое время на ознакомление с другой относящейся к CSS информацией для полного понимания внутренней работы CSS. Для этого отлично подойдет сайт "CSS Zen garden", ссылка на который приведена в разделе "Ресурсы".
 

Листинг 3. Простое CSS-правило
                
body {
   color: maroon;
   font-size: medium;
   text-align: center; 
   background:green; 
}
		

Рисунок 2. Типы CSS-селекторов
Рисунок 2. Типы CSS-селекторов

Некоторые интегрированные среды Ajax предоставляют абстракцию библиотек исходного кода для операций ввода/вывод, сетевых операций, графики, данных и т.д. Основанная на Eclipse программа Aptana обеспечивает отличную интеграцию многих подобных Ajax-сред в одной IDE. Однако рассмотрение этих интегрированных Ajax-сред выходит за рамки данной статьи. Ajax-код для сценария работы банка реализуется при помощи обычного JavaScript, главным образом для того, чтобы упростить работу и дать вам возможность поработать с кодом в приложении напрямую, а не через библиотеки. После завершения работы с данной серией статей вы можете ознакомиться с такими интегрированными Ajax-средами как Dojo, Rico, Script.aculo.us и Prototype.

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

HTTP-взаимодействие в Ajax

В предыдущем разделе были рассмотрены технологии XHTML, CSS и DOM, являющиеся частью Ajax. Кроме них основным для Ajax-приложений является способ выполнения HTTP-взаимодействий (с сервером промежуточного уровня). Ajax обеспечивает новый способ взаимодействия выполняющихся в браузере приложений с серверами в асинхронном стиле. Он значительно отличается от классических Web-приложений в том, что пользователи не ожидают ответа и не видят песочных часов при нажатии кнопки Submit в HTML-форме. Такое асинхронное HTTP-взаимодействие поддерживается объектом XHR, который является объектом браузера, позволяющим JavaScript-коду в браузере выполнять асинхронные HTTP-запросы к серверу. Он позволяет выполнять HTTP-запросы, принимать ответы и обновлять части страницы полностью в фоновом режиме, что приводит к отсутствию видимых для пользователя прерываний работы приложения. Ajax сохраняет чувствительность пользовательского интерфейса во время работы с сервером. В настоящее время ведутся работы в W3C по стандартизации XHR (см. раздел "Ресурсы"). На рисунке 3 показаны различные методы и свойства объекта XHR.

Рисунок 3. Методы и свойства объекта XML HTTP Request (XHR)
Рисунок 3. Методы и свойства объекта XML HTTP Request (XHR)

Ниже приведено общее описание последовательности операций для XHR:

  1. Создать экземпляр объекта XMLHttpRequest.
  2. Использовать объект XMLHttpRequest для выполнения вызова к серверу, определяя функцию обратного вызова, выполняющуюся автоматически при получении ответа от сервера.
  3. Обработать ответ сервера в функции обратного вызова.
  4. При необходимости перейти к шагу 2 для выполнения следующего взаимодействия с сервером в асинхронном режиме.

Я рассмотрю конкретные детали использования XHR в данном сценарии позже.

Строительные блоки Ajax-приложения, выполняющегося в браузере

Наше приложение состоит из следующих артефактов:

  • XHTML-файл
  • CSS-файл
  • XHR JavaScript-файл
  • Бизнес-логика на JavaScript на стороне клиента
  • Необходимые вспомогательные JavaScript-файлы

XHTML-файл - это основной файл, который пользователи указывают в браузерах для начала работы приложения. Этот файл главным образом содержит все элементы пользовательского интерфейса, созданные (в основном) при помощи обычных HTML-тегов, таких как <DIV>, <SPAN>, <TABLE> и <FORM>. Данный файл также использует несколько других браузерных технологий для добавления стилей, взаимодействий и поддержки формата данных.

CSS-файл предоставляет правила для добавления необходимого стилевого оформления элементов пользовательского интерфейса. Поскольку CSS - это огромная тема, в данном сценарии невозможно рассмотреть всю функциональность CSS. Однако я выделю некоторые основные и важные функциональные возможности CSS в контексте данного приложения.

XHR-файл поясняет логику инициализации объекта XMLHttpRequest независимым от браузера способом. Он также объясняет, как можно использовать XHR в асинхронном режиме взаимодействия. Чтобы не изобретать велосипед, мы будем использовать XHR в стиле, широко распространенном в настоящее время в сообществе Web-разработчиков.

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

Кроме того, одним из строительных блоков данного приложения является также программа с открытым исходным кодом для обработки JSON-данных (JavaScript Object Notation).

Реализация портала Bank как Ajax-приложения

Для нашего сценария работы банка требуется простой пользовательский интерфейс, который используют кассиры банка для выполнения основных операций, реализованных нами в PHP-модуле ранее. На момент написания данной статьи простой способ создания основанных на XHTML, CSS и JavaScript приложений, выполняющихся в браузере, предоставляет Aptana Web IDE. Aptana - это бесплатный подключаемый модуль, который интегрируется в среду Eclipse вместе с программой PDT. Он все еще находится в стадии разработки, но доступная в настоящее время промежуточная версия полностью отвечает требованиям нашего сценария. Мы будем реализовывать Ajax-функции в данном сценарии при помощи "чистого" JavaScript, хотя Aptana обеспечивает возможность интеграции с несколькими другими Ajax-библиотеками с открытыми исходными кодами.

Выполните следующие действия для создания одностраничного Ajax-приложения, выполняющегося в браузере:

  1. В Eclipse перейдите в перспективу Aptana: выберите Window->Open Perspective->Other->Aptana и нажмите кнопку OK.
  2. В перспективе Aptana в нижнем левом фрейме выберите закладку Project.
  3. Щелкните правой кнопкой мыши на проекте BankTeller и выберите New->HTML file:
    1. В поле File name введите index.html и нажмите кнопку Finish.
  4. Замените содержимое файла index.html, введя или скопировав исходный код из листинга 4, и выберите File->Save.
  5. Щелкните правой кнопкой мыши на проекте BankTeller и выберите New->CSS file:
    1. В поле File name введите BankTeller.css и нажмите кнопку Finish.
  6. Замените содержимое файла BankTeller.css, введя или скопировав исходный код из листинга 5, и выберите File->Save.
  7. Щелкните правой кнопкой мыши на проекте BankTeller и выберите New->JavaScript file:
    1. В поле File name введите xhr.js и нажмите кнопку Finish.
  8. Замените содержимое этого файла, введя или скопировав исходный код из листинга 6, и выберите File->Save.
  9. Просмотрите комментарии в этих файлах для освоения кода или обратитесь к следующему разделу, в котором приведено общее описание логики программы.

Итак, две третьих части работы с артефактами одностраничного Ajax-приложения сделано! Вы выполните оставшуюся работу в третьей части данной серии статей.

Листинг 4. index.html

                
<!--
============================================================
Проект: Полный цикл разработки Ajax-приложения

Назначение: Это пример сценария, используемого 
 в статье на IBM developerWorks.

Дата последней модификации: 12 мая 2007.

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

В данном файле широко используются теги <DIV>, <SPAN> и 
<TABLE> для выполнения известных Ajax-приемов.
Это самодостаточный файл, содержащий всю 
HTML-разметку, необходимую для приложения кассира банка.
============================================================
-->
<!DOCTYPE HTML PUBLIC 
   "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
   <!--
   Заголовок HTML-документа описывает различные свойства документа, в том числе 
   его название и взаимоотношения с другими документами, такими как CSS и
   JavaScript-файлы.
   В одностраничном приложении Bank Teller будут использоваться следующие документы:
   
   1) json.js (синтаксический анализатор JSON с открытыми исходными кодами,
   разработанный Дугласом Крокфордом (Douglas Crockford)).
   2) xhr.js (базовая программа, предоставляющая XHR-функции).
   3) BankTeller.js (вся логика, выполняющаяся на стороне клиента, находится
   в этом JavaScript-файле)
   4) BankTeller.css (все стилевое оформление, необходимое для приложения,
   находится в этом файле)

   -->
   <head>
      <title>Ajax Bank Teller Application</title>
      <script language"JavaScript" type="text/javascript" src="json.js"></script>      
      <script language"JavaScript" type="text/javascript" src="xhr.js"></script>
      <script language"JavaScript" type="text/javascript" src="BankTeller.js"></script>
      <link rel="stylesheet" style="text/css" href="BankTeller.css" media="all" />
   </head>
   
   <!--
   Установить обработчик событий на инициализацию специфичных для приложения элементов.
   Этот обработчик событий будет активизироваться во время загрузки страницы.
   В данном обработчике событий будет настраиваться начальный экран приложения 
   кассира банка, а другие экраны будут скрыты.

   -->
   <body onload="initOnPageLoad();">
      <!--
      Это приложение имеет семь различных разделов, которые разделяются 
      при помощи элементов <DIV>. Такими разделами являются:
      1) Раздел Main (главный)
      2) Раздел Teller Options (Menu) (операции кассира - меню)
      3) Раздел Deposit (депозит)
      4) Раздел Debit (дебет)
      5) Раздел Stock portfolio (портфель ценных бумаг)
      6) Раздел Bank teller action result (результат операции кассира банка)
      7) Раздел Page footer (сноска страницы)
      
      Все разделы будут показываться избирательно в зависимости от контекста
      приложения, в котором находится пользователь в данный момент времени. Это 
      выполняется логикой на стороне клиента, реализованной на JavaScript. Данный прием 
      обеспечивает динамичность и хорошую реакцию на действия пользователя. Он 
      значительно улучшает работу пользователя в сравнении с традиционными 
      Web-приложениями, использующими модель обновления страницы "нажми и жди".
      -->
      <!-- 
      Следующий главный DIV-контейнер предназначен для всего приложения.
      Все остальные разделы являются его потомками. Все они находятся в 
      этом узле DIV-контейнера в DOM.
      --> 
      <div id="mainPage" 
         title="This is an Ajax based single-page browser application. 
         No browser Back button please. HTML download from server occurs ONLY ONCE. 
         From then on, back-end server is contacted asynchronously ONLY 
         for data exchanges.">
         <h1 title="This web application provides Bank teller services.">
            Bank Teller Operations
         </h1>
   
         <!--
         DIV-элемент tellerOptions содержит таблицу, которая будет предоставлять 
         пользователю доступные для выбора операции кассира. Этот контейнер
         содержит пункты меню для всего приложения кассира банка. 
         -->
         <div id="tellerOptions" title="You can perform these teller operations.">
            <center><font color="Olive">
               <u>Click an operation below</u>
            </font></center>
            
            <!--
            Каждый пункт меню является HTML-текстом, который встроен внутрь тега 
            SPAN. Это позволит нам установить события mouseover и mouseout
            для данного текстового элемента. Мы можем комбинировать эти события с 
            CSS-стилем для эмуляции поведения меню в настольных приложениях. Когда 
            пользователь выбирает данный пункт меню, может быть выполнена
            соответствующая логика приложения.
            
            Эти различные пункты меню находятся внутри тега TABLE 
            в виде отдельных строк.
            -->
            <table class="tellerOptionsTable" align="center">
               <tr>
                  <!--
                  Обратите внимание на то, что мы используем атрибут class в span 
		для стилевого оформления значения td, чтобы можно было динамически 
		назначать классы стиля на JavaScript) при перемещении пользователем 
		курсора мыши по различным названиям операций. Если использовать 
		атрибут id для CSS-стиля, динамически назначать стили на JavaScript 
		нельзя.
                  -->
                  <!--
                  Обработчики событий вызываются с параметром,
                  указывающим номер пункта меню, т.е. 1, 2 или 3. 
                  i.e. 1, 2 or 3. 
                  -->                  
                  <!-- Пункт меню Deposit -->
                  <td>
                     <span id="tellerOptionsLink1" class="tellerOptionsLink" 
                     title="Deposit to an account."
                     onmouseover="changeOptionsLinkStyleForSelection(1);" 
                     onmouseout="changeOptionsLinkStyleToDefault(1);"
                     onclick="processTellerOperation(1);">
                        1) Deposit to an account
                     </span>
                  </td>
               </tr>

               <tr>
                  <!-- Пункт меню Debit -->
                  <td>
                     <span id="tellerOptionsLink2" class="tellerOptionsLink" 
                     title="Debit from an account."
                     onmouseover="changeOptionsLinkStyleForSelection(2);" 
                     onmouseout="changeOptionsLinkStyleToDefault(2);"
                     onclick="processTellerOperation(2);">
                        2) Debit from an account
                     </span>
                  </td>
               </tr>            

               <tr>
                  <!-- Пункт меню Get stock portfolio value -->
                  <td>
                     <span id="tellerOptionsLink3" class="tellerOptionsLink" 
                     title="Get Stock portfolio value."
                     onmouseover="changeOptionsLinkStyleForSelection(3);" 
                     onmouseout="changeOptionsLinkStyleToDefault(3);"
                     onclick="processTellerOperation(3);">
                        3) Current portfolio value
                     </span>
                  </td>
               </tr>                           
            </table>
         </div> <!-- Конец  <div id="tellerOptions"> -->

         <!--
         DIV-элемент tellerOptions содержит HTML-форму, необходимую для 
         депозита денег на счет. 
         -->
         <div id="depositAction" title="Deposit to an account" class="depositAction">
            <!-- Эта HTML-форма предоставляет UI-элементы для операции депозита -->
            <form name="depositActionForm">
               <span id="depositActionFormTitle" class="depositActionFormTitle">
                  Deposit to an account
               </span>

               <!-- 
               Данная таблица реализует ниспадающее окно списка вариантов и 
               элемент input для ввода суммы депозита.
               -->
               <table class="depositActionTable" align="center">
                  <tr>
                     <td>Select Account Owner:</td>
                     <td>
                        <select id="depositAccountOwner" size=7 
                           name="depositAccountOwner">
                        </select>
                     </td>
                  </tr>
                  
                  <tr>
                     <td>Amount:</td>
                     <td><input type="text" name="depositAmount" 
                        id="depositAmount" maxlength="15" size="15" 
                        title="e-g: 100"/></td>
                  </tr>
               </table>
               
               <!--
               Данная таблица реализует кнопку операции. При ее нажатии будет 
               активизироваться обработчик события onclick, выполняющий 
               необходимую логику на стороне клиента.
               -->
               <table align="center" class="depositAmountButtonTable">
                  <tr>
                     <td>
                        <input id="depositAmountActionButton" 
                           value="Deposit" type="button" 
                           title="Click here to deposit to an account." 
                           onclick="depositAmountAction_Async();"/>
                     </td>                     
                  </tr>               
               </table>               
            </form>
         </div> <!-- Конец <div id="depositAction"> -->

         <!--
         DIV-элемент tellerOptions содержит HTML-форму, необходимую для 
         дебета денег со счета. 
         -->
         <div id="debitAction" title="Debit from an account" class="debitAction">
            <form name="debitActionForm">
               <span id="debitActionFormTitle" class="debitActionFormTitle">
                  Debit from an account
               </span>
               
               <table class="debitActionTable" align="center">               
                  <tr>
                     <td>Select Account Owner:</td>                     
                     <td>
                        <select id="debitAccountOwner" size=7 
                           name="debitAccountOwner">
                        </select>
                     </td>
                  </tr>                  

                  <tr>
                     <td>Amount:</td>
                     <td><input type="text" name="debitAmount" id="debitAmount"
                        maxlength="15" size="15" title="e-g: 100"/></td>
                  </tr>
               </table>

               <!--
               Данная таблица реализует кнопку операции. При ее нажатии будет 
               активизироваться обработчик события onclick, выполняющий 
               необходимую логику на стороне клиента.
               -->               
               <table align="center" class="debitAmountButtonTable">
                  <tr>
                     <td>
                        <input id="debitAmountActionButton" value="Debit"
                           type="button" 
                           title="Click here to debit from an account." 
                           onclick="debitAmountAction_Async();"/>
                     </td>                     
                  </tr>               
               </table>               
            </form>
         </div> <!-- Конец <div id="depositAction"> -->

         <!--
         DIV-элемент portfolioOption содержит форму для 
         обновления текущего значения стоимости портфеля ценных бумаг.
         -->
         <div id="portfolioAction" title="Update portfolio value"
            class="portfolioAction">
            <form name="portfolioActionForm">
               <span id="portfolioActionFormTitle" class="portfolioActionFormTitle">
                  Get portfolio value
               </span>
               
               <table class="portfolioActionTable" align="center">
                  <tr>   
                     <td>Select Account Owner:</td>
                     <td>
                        <select id="portfolioAccountOwner" size=7 
                           name="portfolioAccountOwner">
                        </select>
                     </td>
                  </tr>
               </table>

               <!--
               Данная таблица реализует кнопку операции. При ее нажатии будет 
               активизироваться обработчик события onclick, выполняющий 
               необходимую логику на стороне клиента.
               -->               
               <table align="center" class="portfolioValueButtonTable">
                  <tr>
                     <td>
                        <input id="portfolioActionButton" 
                           value="Get Stock Portfolio Value" type="button" 
                           title="Click here to get the portfolio value." 
                           onclick="portfolioAction_Async();"/>
                     </td>                     
                  </tr>               
               </table>               
            </form>
         </div> <!-- Конец <div id="portfolioAction"> -->

         <!--
         DIV-элемент tellerActionResult содержит форму для
         получения значения стоимости портфеля ценных бумаг.
         -->
         <div id="tellerActionResult" title="Result of teller operation"
            class="tellerActionResult">
            <form name="tellerActionResultForm">
               <span id="tellerActionResultTitle" class="tellerActionResultTitle">
                  Result from Teller Operation
               </span>

               <!--
               В данной таблице содержится текстовая область, суммирующая результаты
               операции, выполненной кассиром.
               -->               
               <table class="tellerActionResultTable" align="center">
                  <tr>
                     <td>
                        <textarea id="tellerActionResultArea" rows="15"
                           cols="80" readonly></textarea>
                     </td>
                  </tr>
               </table>               
            </form>
         </div> <!-- Конец <div id="tellerActionResult"> -->
         
         <!--
         DIV-элемент pageFooter содержит информацию сноски, такую как 
         имя работающего с программой пользователя и ссылку Go to main menu. Этот
         раздел сноски будет прикрепляться к нижней части каждого раздела  
         данного приложения.
         -->
         <div id="pageFooter">
            <h1></h1>

            <table id="footerTable" class="footerTable" align="center">
               <tr>
                  <!--
                  Обратите внимание на то, что мы используем атрибут class в span 
		для стилевого оформления значения td, чтобы можно было динамически 
		назначать классы стиля на JavaScript при перемещении пользователем 
		курсора мыши по различным названиям операций. Если использовать 
		атрибут id для CSS-стиля, динамически назначать стили на JavaScript 
		нельзя.
                  -->
                  <td>
                     <span id="currentUserDisplay" class="currentUserDisplay" 
                        title="Currently logged in user's name.">
                        Teller: John Doe 
                     </span>
                  </td>
                  
                  <!--
                  Обработчики событий вызываются с параметром, 
                  указывающим, какой пункт сноски выделяется.

                  -->
                  <td>
                     <span id="goToMainMenuLink" class="goToMainMenuLink" 
                     title="Click here to go to main menu."
                     onmouseover="changeFooterLinkStyleForSelection(1);" 
                     onmouseout="changeFooterLinkStyleToDefault(1);"
                     onclick="processFooterOperation(1);">                  
                        Back to main menu
                     </span>
                  </td>                  
               </tr>
            </table>            
            
         </div> <!-- Конец <div id="PageFooter"> -->               
      </div> <!-- Конец <div id="mainPage"> -->
   </body>
</html>
		

Листинг 5. BankTeller.css

                
/*
============================================================
Проект: Полный цикл разработки Ajax-приложения

Назначение: Это пример сценария, используемого 
 в статье на IBM developerWorks.

Дата последней модификации: 12 мая 2007.

Данный файл реализует CSS-правила, использующиеся для стилевого оформления 
UI-элементов управления выполняющегося в браузере приложения кассира банка.
Данное приложение не использует все возможности 
CSS. Однако в него включены базовые (и важные) аспекты CSS.
============================================================
*/
/*
Это HTML-селектор, переопределяющий атрибуты 
font и background тега body.
*/
body {
   font-family:       Verdana, Geneva, Arial, sans-serif;
   font-size:         medium;
   text-align:        center;
   background:        rgb(220, 220, 220);
}

/*
Это HTML-селектор, использующийся для стилевого оформления элемента <h1>.
*/
h1 {
   color:             #cc6600;
   border-bottom:     medium double #888888;
   font-size:         1.7em;
}

/*
Этот класс CSS ID применим для DIV-элемента, содержащего таблицу, в которой 
перечислены все операции, доступные кассиру.
*/
#tellerOptions {
   margin-left:       3%;
   margin-right:      3%;
   padding-top:       10px;
   padding-bottom:    10px;
   border:            red;
   border-width:      thick;
   border-style:      double;
   border-collapse:   collapse;     
}

/*
Этот неполный зависимый селектор применяет стиль к таблице операций кассира.
*/
table.tellerOptionsTable td {
   padding-top:       10px;
   padding-bottom:    10px;
}

/*
Этот групповой (generic) CSS-класс применяется для стилевого оформления полей таблиц
в форме операций кассира.
*/
.tellerOptionsTable,
.depositActionTable, 
.debitActionTable,
.portfolioActionTable {
   margin-top:        30px;   
}

/*
Этот групповой CSS-класс применяется для SPAN-элементов (нажимаемый 
текст), предоставляющих различные операции кассира. Пользователь может выбрать из 
этого списка операцию для выполнения. Групповой класс используется здесь так, чтобы
внутри JavaScript мы могли динамически изменять стиль текста операции кассира при  
возникновении событий mouseover или mouseout в текстовых областях, 
определенных элементами SPAN. Ниже указан стиль по умолчанию, когда нет 
активности мыши или когда пользователь выводит курсор мыши из области.
*/
.tellerOptionsLink {
   cursor:            pointer;
   background-color:  rgb(220, 220, 220);
   color:             blue;
   font-weight:       bold;
   font-style:        normal;
   text-decoration:   none;
   border-style:      none;
}

.tellerOptionsLinkSelection,  
.goToMainMenuLinkSelection {
   cursor:            pointer;
   background-color:  green;
   color:             yellow;
   font-weight:       normal;
   font-style:        italic;
   text-decoration:   none;
   border-style:      ridge;
   border-width:      7px;
   border-top-width:  7px;
   border-color:      black;   
}

.depositAction {
   margin-left:       3%;
   margin-right:      3%;
   padding-top:       10px;
   padding-bottom:    10px;
   cursor:            default;
   border:            navy;
   border-width:      thick;
   border-style:      double;
   border-collapse:   collapse;
}

.debitAction {
   margin-left:       3%;
   margin-right:      3%;
   padding-top:       10px;
   padding-bottom:    10px;   
   cursor:            default;
   border:            lightseagreen;
   border-width:      thick;
   border-style:      double;
   border-collapse:   collapse;
 }

.portfolioAction {
   margin-left:       3%;
   margin-right:      3%;
   padding-top:       10px;
   padding-bottom:    10px;
   cursor:            default;
   border:            purple;
   border-width:      thick;
   border-style:      double;
   border-collapse:   collapse;
}

.tellerActionResult {
   margin-left:       3%;
   margin-right:      3%;
   padding-top:       10px;
   padding-bottom:    10px;
   cursor:            default;
   border:            rosybrown;
   border-width:      thick;
   border-style:      double;
   border-collapse:   collapse;
}

/*
Этот групповой CSS-класс определяет формат заполнения ячейки (cell padding) для 
кнопки в форме операций кассира и других.
*/
table.depositAmountButtonTable td, 
table.debitAmountButtonTable td,
table.portfolioValueButtonTable td, 
table.tellerActionResultTable td {
   padding-left:      25px;
   padding-right:     25px;   
}

/*
Этот групповой CSS-класс определяет поле для таблиц, содержащих 
форму операций кассира, и других.
*/
table.depositAmountButtonTable,
table.debitAmountButtonTable,
table.portfolioValueButtonTable,
table.tellerActionResultTable {
   margin-top:        40px;
}

.depositActionFormTitle,
.debitActionFormTitle, 
.portfolioActionFormTitle,
.tellerActionResultTitle {
   color:             olive;
   font-weight:       normal;
   font-style:        normal;
   text-decoration:   underline;
   text-align:        center;
}

/*
Этот групповой CSS-класс применяется для установки формата заполнения ячейки 
для элементов <td> таблицы, в которой перечислены пункты сноски.
*/
table.footerTable td {
   padding-left:      100px;
   padding-right:     100px;
}

/*
Этот групповой CSS-класс применяется для установки поля 
таблицы, в которой перечислены пункты сноски.
*/
table.footerTable {
   margin-top:        20px;
}

/*
Этот групповой CSS-класс применяется для статического отображения имени 
пользователя, зарегистрировавшегося для работы в системе.
*/
.currentUserDisplay {
   cursor:            default;
   background-color:  rgb(220, 220, 220);
   color:             darkred;
   font-weight:       normal;
   font-style:        normal;
}

/*
Этот групповой CSS-класс применяется для SPAN-элементов (нажимаемый 
текст), предоставляющих различные операции кассира. Пользователь может выбрать из 
этого списка операцию для выполнения. Групповой класс используется здесь так, чтобы
внутри JavaScript мы могли динамически изменять стиль текста операции кассира при  
возникновении событий mouseover или mouseout в текстовых областях, 
определенных элементами SPAN. Ниже указан стиль по умолчанию, когда нет 
активности мыши или когда пользователь выводит курсор мыши из области.
*/
.gotoMainMenuLink {
   cursor:            pointer;
   background-color:  rgb(220, 220, 220);
   color:             black;
   font-weight:       bold;
   font-style:        normal;
   text-decoration:   none;
   border-style:      none;
}

/*
Этот CSS-класс ID применяется для кнопки Teller actions и других.
*/
#depositAmountActionButton, 
#debitAmountActionButton,
#portfolioActionButton {
   background-color:  saddlebrown;   
   color:             white;   
   border:            purple;
   border-width:      thin;
   border-style:      inset;
   font-weight:       bold;
   font-style:        normal;   
   font-size:         90%
}

/*
Ниже переопределяется стиль, определенный ранее в этом же классе.
*/
#debitAmountActionButton {
   background-color:  midnightblue;   
}

/*
Ниже переопределяется стиль, определенный ранее в этом же классе.
*/
#portfolioActionButton {
   background-color:  teal;   
}
		

Listing 6. xhr.js

                
/*
============================================================
Проект: Полный цикл разработки Ajax-приложения

Назначение: Это пример сценария, используемого 
 в статье на IBM developerWorks.

Дата последней модификации: 12 мая 2007.

Данный JavaScript-файл реализует специфичные функции, связанные с объектом
XHR (XML HTTP Request). Используемая здесь логика является
типовой, применяемой в сотнях других Web-приложениях. Ее можно обнаружить
в любой книге по Ajax или статье, объясняющей внутреннюю работу XHR. 
============================================================
*/

/*
============================================================
Функция: createRequest

Дата последней модификации: 12 мая 2007.

Эта функция создает XHR-объект, независимый от браузера.
На момент написания данной статьи XHR некоторым образом
зависит от браузера. Можно разделить поддержку XHR браузерами на два
основных класса, т.е. Internet Explorer и не-IE браузеры.
Далее, внутри IE имеется несколько реализаций 
XHR для браузеров до IE6 и для остальных. Итак, Microsoft 
поддерживает XHR через свой ActiveX. После выслушивания 
жалоб со стороны сообщества Web-разработчиков, Microsoft
осознала необходимость реализации XHR как родного для браузера
объекта, как сделано в остальных браузерах. По слухам мы скоро
увидим XHR-поддержку в IE-браузерах через 
объект XMLHttpRequest. А пока мы должны позаботиться о
зависимости от браузеров, как показано в этой функции.
============================================================
*/
function createRequest() {
   // Определить локальную переменную и установить ее в значение null.
   var request = null;

   // Попробовать различные способы определения типа используемого браузера.   
   try {
      // Не Microsoft браузеры (Firefox, Safari и др.).
      request = new XMLHttpRequest();
   } catch(trymicrosoft) {
      try {
         // IE6 и выше.
         request = new ActiveXObject("Msxml2.XMLHTTP");
      } catch(othermicrosoft) {
         try {
            // Более старые версии IE, т.е. до IE6.
            request = new ActiveXObject("Microsoft.XMLHTTP");
         } catch(failed) {
            // Нет поддержки XHR
            request = null;
         } // Конец catch(failed)
      } // Конец catch(othermicrosoft)
   } // Конец catch(trymicrosoft)
   
   // Проверить, есть ли корректный объект XHR.  
   if (request == null) {
      alert("Error creating the XMLHttpRequest object!");
   } else {
      // Возвратить объект XHR, который был создан.
      return(request);
   } // Конец if (request == null)
} // Конец function createRequest.

/*
============================================================
Функция: sendHttpRequest

Дата последней модификации: 12 мая 2007.

Данная функция передает любые произвольные данные на сервер.
Принимает четыре аргумента:
1) Объект XHR.
2) Функцию обратного вызова (если есть) для получения ответа сервера.
3) URL, по которому нужно передать содержимое.
4) Передаваемые данные.

В зависимости от входных параметров для обмена данными она будет 
взаимодействовать с сервером в асинхронном режиме или
синхронно. Используется POST-запрос
HTTP для выполнения вызова в REST-стиле.
============================================================
*/
function sendHttpRequest(request, callbackFunction, url, postData) {
   // Инициализация локальной переменной в значение false.
   // Эта переменная указывает, нужно ли взаимодействовать с сервером 
   // в асинхронном режиме.
   var async_request = false;
   
   // Имеется ли функция обратного вызова?  
   if (callbackFunction != null) {
      // Функция обратного вызова есть.
      // Установить эту функцию как свойство onreadystatechange объекта XHR.
      request.onreadystatechange = callbackFunction;
      // Установить локальную переменную на указание того, что 
      // нам необходимо передать и принять ответ в не блокирующем режиме,
      // т.е. асинхронно. 
      async_request = true;
   }
   
   // Открыть HTTP-соединение по предоставленному URL.
   request.open("POST", url, async_request);
   // Установить заголовок HTTP-запроса.
   request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
   // Передать его.
   // Если async-режим не равен true, этот вызов будет блокировать работу
   // до получения HTTP-ответа от сервера.
   // В противном случае это выражение просто передаст данные и возвратит 
   // управление, не ожидая ответа от сервера.          
   var response = request.send(postData);   
   
   if (async_request == false) {
      // Мы передаем запрос в синхронном режиме. 
      // Следовательно, возвращаем ответ, полученный от сервера.
      return(response);
   } else {
      // Если запрос был передан в асинхронном режиме, мы не должны заботиться о 
      // возврате чего-нибудь стоящего. Ответ будет передан прямо в функцию
      // обратного вызова, предоставленную вызывающей программой.
      return(true);
   }   
} // Конец функции sendHttpRequest.
		

Логика выполняющегося в браузере приложения Bank

Вы создали XHTML- и CSS-артефакты вместе со вспомогательным файлом, работающим с XHR. XHTML-файл (index.html) является точкой входа в приложение. Это единственный файл, который пользователь приложения кассира банка будет видеть в адресной строке браузера. Все остальные артефакты передаются с сервера в сессию браузера вместе с файлом index.html. Если вы посмотрите в раздел <HEAD> файла index.html, то увидите следующие HTML-теги:

<script language="JavaScript" type="text/javascript" src="xyz.js"></script>

Тег script определяет один или несколько сценариев, которые могут активизироваться элементами внутри документа. В данном частном случае он загружает JavaScript-код из внешнего файла. Вот как загружается JavaScript-код приложения из трех различных JavaScript-файлов:

<link rel="stylesheet" type="text/css" href="xyz.css" media="all" />

Тег link объединяет CSS-правила, определенные во внешнем файле. Это выражение подключает указанный CSS-файл к HTML-документу, влияя таким образом на стиль всего Web-приложения.

Все артефакты, связанные с отображаемым содержимым приложения кассира банка, загружаются один раз во время начала работы приложения. Это означает, что необходимо разделить отдельные экраны, которые будут частью приложения, таким образом, чтобы их можно было отображать пользователю при необходимости. Это легко делается путем включения всех элементов пользовательского интерфейса для экрана внутрь отдельного элемента <DIV>. Все приложение полностью находится внутри элемента <DIV> под названием mainPage. В данном приложении имеется шесть других разделов, каждый из которых помещен в свой собственный элемент <DIV>. В свою очередь, все эти шесть <DIV>-разделов заключены в элемент <DIV> mainPage. Организуя эти различные UI-разделы подобным образом, мы облегчаем управление ими посредством управления объектами DOM, что будет рассмотрено в третьей части данной серии статей.

Во время загрузки приложения обработчик событий (initOnPageLoad) устанавливается в элементе <body>. Этот обработчик событий вызывается в самом начале, так чтобы можно было установить содержимое начального экрана для отображения пользователю. Аналогично тегу <DIV>, теги <SPAN> тоже приносят пользу, устанавливая меню подобную структуру в начальном экране просто путем использования обычного текста, заключенного в тег <SPAN>. Обратите внимание на то, что элементы <SPAN> имеют три различных обработчика событий, связанных с мышкой. Эти обработчики событий просто используют правила CSS-стиля для выделения (и снятия выделения) элементов меню. При нажатии на один из элементов меню, соответствующий обработчик события onclick делает соответствующий экран видимым в окне браузера. Все происходит без обращения к серверу, а лишь путем локального управления объектами DOM браузера. Еще одним интересным элементом пользовательского интерфейса является раздел сноски, которая прикрепляется к нижней части каждого экрана. В каждый момент времени схема экрана содержит <DIV>-элементы mainPage и pageFooter. Какой-либо из остальных разделов (депозит, дебет, портфель ценных бумаг и результаты операции кассира банка) помещается между этими двумя элементами <DIV> - еще один гибкий принцип Ajax-проектирования.

CSS-файл (BankTeller.css) содержит CSS-правила, которые применяются к элементам пользовательского интерфейса данного приложения. В этих CSS-правилах продемонстрировано несколько функциональных возможностей CSS. В данном приложении широко используются HTML-селекторы, ID-селекторы и class-селекторы.

Например, в самом начале CSS-файла HTML-селектор body переопределяет шрифт и цвет фона всей страницы приложения. В конце CSS-файла используется portfolioActionButton, который является ID-селектором. Он указывает, что все HTML-теги, определенные в HTML-файле (index.html) с этой строкой ID, будут иметь стиль, определенный CSS-правилом #portfolioActionButton. Аналогично, вы можете увидеть использование селектора классов в CSS-правиле .tellerOptionsLink, которое применяется ко всем HTML-тегам, присвоенным классу tellerOptionsLink. Также обратите внимание на использование групповых селекторов, в которых сгруппированы два или более селекторов для объявлений одного стиля. Примеры групповых селекторов имеются во многих местах файла BankTeller.css. В нижней части CSS-файла можно увидеть, что все три кнопки операции сгруппированы вместе (#depositAmountActionButton, #debitAmountActionButton и #portfolioActionButton) для получения одинаковых свойств стиля кнопки. Демонстрируется также еще одна продвинутая функциональная возможность CSS, называемая дочерним селектором (descendant selector). Дочерний селектор позволяет оформлять стилем отдельные дочерние элементы в зависимости от их родительского селектора. Пример показан в CSS-файле для стилевого оформления всех столбцов таблицы (например, <td> в <table>) путем назначения специфичного отступа в ячейках (cell padding). Найдите table.depositAmountButtonTable td и вы увидите применение дочерних селекторов. CSS-синтаксис HTML, ID и class-селекторов можно увидеть на рисунке 2.

XHR-файл (xhr.js) содержит две функции. Одна создает объект XHR, который можно использовать для обмена (передачи и приема) информацией с сервером промежуточного уровня. Функция createRequest подготавливает объект XHR. На момент написания данной статьи XMLHttpRequest по-разному поддерживался в различных браузерах. В частности, два самых популярных браузера (Internet Explorer (IE) и Mozilla Firefox) поддерживают XHR по-своему.

XHR по-разному реализован в различных версиях IE. Данная функция пытается создать объект XHR, используя специфичную для браузера технику. В браузерах, отличных от IE, объект XHR может быть создан просто путем инициализации встроенного класса XMLHttpRequest. В IE версии 6.0 и выше это делается при помощи ActiveXObject и XMLHTTP в Msxml2. Во всех остальных версиях IE это делается при помощи ActiveXObject и другого экземпляра объекта XMLHTTP. Microsoft работает над реализацией XHR-поддержки, аналогичной имеющейся в других браузерах. Пока этого не произошло, логика, применяемая в данной функции, необходима. Другая функция (sendHttpRequest) используется для передачи любых произвольных данных в сервис промежуточного уровня. Она взаимодействует с сервисом промежуточного уровня, используя механизм доступа в стиле REST при помощи запроса POST HTTP. Эта функция принимает следующие четыре параметра:

  • Объект XHR.
  • Функция обратного вызова (null, если функция обратного вызова не нужна).
  • URL, по которому нужно передать данные.
  • Данные (специфичные для приложения данные, которые нужно передать в сервис промежуточного уровня).

Метод sendHttpRequest передает данные в сервис промежуточного уровня, используя объект XHR. Если указана функция обратного вызова, он устанавливает свойство onreadystatechange объекта XHR в эту функцию. Затем он открывает HTTP-соединение с сервисом промежуточного уровня. В этот сервис передаются все необходимые HTTP-заголовки и данные. Если указана функция обратного вызова, метод send объекта XHR возвращает управление немедленно, не ожидая ответа сервера. В данном случае ответ сервера обрабатывается функцией обратного вызова после приема ответа браузером. Подробная информация об этом будет приведена в третьей части данной серии статей. Если функция обратного вызова не предоставляется, функция send блокирует работу и ждет получения ответа от сервера, или генерирует HTTP-ошибки.

Код выполняющегося в браузере одностраничного приложения, приведенный в листинге 4, генерирует простой интерфейс для браузера, используемый кассирами банка при выполнении банковских операций, таких как депозит, дебет и вычисление стоимости портфеля ценных бумаг. Как часть данного одностраничного Web-приложения код предоставляет три формы. Каждая из этих трех форм реализует ниспадающий HTML-список, заполняемый именами владельцев счетов. Затем реализуется редактируемое поле для ввода суммы депозита или дебета. Три кнопки позволяют кассиру банка выполнить требуемую банковскую операцию. Три кнопки в этом приложении спроектированы для доступа к REST-сервису промежуточного уровня, который вы разработаете в третьей части данной серии статей. До тех пор пока вы не разработаете PHP-модуль, функции кассира банка не будут функционировать. Также в третьей части вы разработаете JavaScript-файл (BankTeller.js).

Для разработки нашего сценария работы банка было использовано много новых Ajax-приемов. Если вы начинающий Web-разработчик, данная статья познакомила вас с несколькими новыми областями. Вы должны тщательно проанализировать частично готовый код браузера и код промежуточного уровня для полного понимания их работы. Если вы продвинутый Web-разработчик, то можете попытаться завершить оставшиеся задачи (перечисленные в следующем разделе) самостоятельно. Не переживайте, если не сможете этого сделать - третья часть данной серии статей поможет вам.

Заключение

Вы создали основные части сценария работы банка. Хотя пройдена только часть полного цикла разработки Ajax-приложения, вы узнали о базе данных, базовой бизнес-логике, встроенной в PHP-модуль, и пользовательском Web-интерфейсе, передаваемом посредством Ajax-технологий. Для полного завершения разработки сценария должны еще быть созданы следующие элементы (см. схему сценария работы банка, приведенную в первой части данной серии статей):

  • JavaScript-модуль (содержащий логику, выполняющуюся на стороне клиента, и логику асинхронного взаимодействия) для выполнения обмена данными с сервером.
  • PHP-модуль, содержащий код SOAP-клиента удаленного Web-сервиса котировок акций.
  • PHP-модуль, выступающий в роли REST-сервиса для вызовов бизнес-логики в других PHP-модулях, которые являются частью данного сценария.
  • Интеграция всех артефактов всего сценария.
  • Реализация и тестирование всего Ajax-PHP-сценария.
  • Отладка завершенного сценария с использованием отладчиков на стороне клиента и на стороне сервера.

Информация, приведенная в первой и второй частях данной серии статей, познакомила вас с богатыми возможностями технологий с открытыми исходными кодами, имеющимися для всех трех уровней, а также помогла понять работу отдельных инструментальных средств Eclipse для Ajax-PHP-разработки. Артефакты кода для создания DB на SQL, серверной логики приложения на PHP и Bank Portal (XHTML, CSS и XHR) находятся в загружаемом файле, прилагающемся к данной статье. Следите за выходом третьей и последней части данной серии статей. А пока углубитесь в изучение базовых концепций Ajax и PHP (а также технологий разработки), уже примененных в сценарии работы банка.

Загрузка

wa-aj-end2end2.zip (14 KB)

Ресурсы

Получить продукты и технологии


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=8661