AJAX для ASP.NET и шаблоны на стороне клиента - Прием проектирования: применение шаблонов на стороне обозревателя

ОГЛАВЛЕНИЕ

Прием проектирования: применение шаблонов на стороне обозревателя

Цель применения шаблона проектирования BST в том, чтобы отделить код, создающий представления данных, от самих данных. Будучи старой проблемой программных систем, отделение представления от данных имеет классическое решение: любую разновидность шаблона проектирования Model View Controller (MVC).

Хотя применение приемов проектирования MVC и BST ни в коем случае не является взаимоисключающим, BST можно рассматривать как MVC, в котором нет никаких упоминаний о котроллере и в котором разделение между представлением и моделью осуществляется с помощью физического разделения обозревателя и сервера. На рис. 5можно посмотреть диаграмму стадий, входящих в BST. Пользователь вызывает удаленный вызов, который загружает какие-то данные на клиента. Данные управляются функцией обратного вызова JavaScript, которая создает новое поколение компонент — построители разметки.

 

Рис. 5 Шаблоны на стороне обозревателя в действии

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

Теперь посмотрим на код. В этой реализации основная логика BST находится в JavaScript-классе MarkupBuilder, который принимает до трех HTML-шаблонов: верхний колонтитул, нижний колонтитул и элемент. К этим шаблонам можно напрямую обратиться из модели DOM или указать их как обычные текстовые строки:

function pageLoad()
{
   if (builder === null)
   {
     builder = new Samples.MarkupBuilder();
     builder.loadHeader($get("header"));
     builder.loadFooter($get("footer"));
     builder.loadItemTemplate($get("item"));
  }

C помощью невидимых DIV HTML-шаблоны можно встраивать прямо в страницу. Правда, это работает только тогда, когда у разметки блока правильный формат. Лучше встраивать шаблоны в островки данных XML, как показано здесь:

<xml id="item">
   <tr style="background-color:#F0FAFF;">
     <td align="left">#Symbol</td>
     <td align="right">#Quote</td>
     <td align="right">#Change</td>
   </tr> 
</xml> 

Как можно заметить, шаблон – это блок HTML, который ссылается на привязанные поля данных с помощью выбранной нотации. В данном случае я воспользовался выражением #PropertyName, чтобы указать на местозаполнитель для привязанного значения. Когда данные готовы, остается только вызвать метод bind для построителя разметки:

function _getLiveQuotes()
{
   Samples.WebServices.LiveQuoteService.Update(onDataAvailable);
}
function onDataAvailable(results)
{
   var temp = builder.bind(results);
   $get("grid").innerHTML = temp;

Незачем и упоминать, что именованная сетка элементов является местозаполнителем всего вывода данных в этом примере. Класс MarkupBuilder основан на клиентской библиотеке Microsoft AJAX. Его полный исходный код доступен на рис. 6.

Рис. 6 класс MarkupBuilder

Type.registerNamespace('Samples');

// Class ctor
Samples.MarkupBuilder = function Samples$MarkupBuilder() 
{
   Samples.MarkupBuilder.initializeBase(this);

   // Initializes private members
   this._header = "";
   this._footer = "";
   this._itemTemplate = "";
}
Samples.MarkupBuilder = function Samples$MarkupBuilder(header, footer) 
{
  Samples.MarkupBuilder.initializeBase(this);

   // Initializes the private members
   this._header = header;
   this._footer = footer;
   this._itemTemplate = "";
}

// PROPERTY:: header (String)
function Samples$MarkupBuilder$get_header() { 
   if (arguments.length !== 0) throw Error.parameterCount();
   return this._header;
}
function Samples$MarkupBuilder$set_header(value) {
   var e = Function._validateParams(arguments, [{name: 'value', 
     type: String}]);
   if (e) throw e;

   this._header = value;
}

// PROPERTY:: footer (String)
function Samples$MarkupBuilder$get_footer() { 
   if (arguments.length !== 0) throw Error.parameterCount();
   return this._footer;
}
function Samples$MarkupBuilder$set_footer(value) {
   var e = Function._validateParams(arguments, [{name: 'value', 
     type: String}]);
   if (e) throw e;

   this._footer = value;
}

// PROPERTY:: itemTemplate (String)
function Samples$MarkupBuilder$get_itemTemplate() { 
   if (arguments.length !== 0) throw Error.parameterCount();
   return this._itemTemplate;
}
function Samples$MarkupBuilder$set_itemTemplate(value) {
   var e = Function._validateParams(arguments, [{name: 'value', 
     type: String}]);
   if (e) throw e;

   this._itemTemplate = value;
}

// METHOD:: bind()
function Samples$MarkupBuilder$bind(data) {
  var temp = this._generate(data);
  return temp;
}
// METHOD:: loadHeader()
function Samples$MarkupBuilder$loadHeader(domElement) {
  var temp = domElement.innerHTML;
  this._header = temp;
}

// METHOD:: loadFooter()
function Samples$MarkupBuilder$loadFooter(domElement) {
  var temp = domElement.innerHTML;
  this._footer = temp;
}

// METHOD:: loadItemTemplate()
function Samples$MarkupBuilder$loadItemTemplate(domElement) {
  var temp = domElement.innerHTML;
  this._itemTemplate = temp;
}

///////           ///////
/////// PRIVATE members  ///////
///////           ///////

function Samples$MarkupBuilder$_generate(data) {
   var _builder = new Sys.StringBuilder(this._header);

   for(i=0; i<data.length; i++)
   {
     var dataItem = data[i];
     var template = this._itemTemplate;


     var pattern = /#\w+/g; // Finds all #word occurrences
     var matches = template.match(pattern); 
     for (j=0; j<matches.length; j++)
     {
       var name = matches[j];
       name = name.slice(1);
       template = template.replace(matches[j], dataItem[name]);
     }

     _builder.append(template);
   }

   _builder.append(this._footer);
   return _builder.toString();
}

// PROTOTYPE
Samples.MarkupBuilder.prototype = 
{
   get_header:     Samples$MarkupBuilder$get_header,
   set_header:     Samples$MarkupBuilder$set_header,
   get_footer:     Samples$MarkupBuilder$get_footer,
   set_footer:     Samples$MarkupBuilder$set_footer,
   get_itemTemplate:  Samples$MarkupBuilder$get_itemTemplate,
   set_itemTemplate:  Samples$MarkupBuilder$set_itemTemplate,
   bind:        Samples$MarkupBuilder$bind,
   _generate:     Samples$MarkupBuilder$_generate,
   loadHeader:     Samples$MarkupBuilder$loadHeader,
   loadFooter:     Samples$MarkupBuilder$loadFooter,
   loadItemTemplate:  Samples$MarkupBuilder$loadItemTemplate
}

Samples.MarkupBuilder.registerClass('Samples.MarkupBuilder');

Для составления строки HTML внутри построителя разметки используется объект Sys.StringBuilder. Сначала он добавляет шаблон заголовка (если он есть), затем выполняет цикл по связанным данным и в конце добавляет шаблон нижнего колонтитула. В этой реализации верхний и нижний колонтитулы не связаны с данными. (На рис. 7 показана страница, использующая построитель разметки.)

 

Рис. 7 Новая страница, составленная с помощью построителя разметки