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 Новая страница, составленная с помощью построителя разметки