Cache + jQuery. Быстрый старт

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

Предполагается, что читатель обладает средним уровнем знаний HTML, CSS и Javasсript и имеет минимальный опыт работы с InterSystems Cache. Загрузить последнюю версию Caché можно здесь. Начальный опыт работы с Caché можно обрести тут.

Зачем нужен jQuery в CSP-приложении?

CSP(Zen) – базовая технология, предлагаемая Caché для быстрого создания AJAX веб-приложений, работающих с базой данных Caché.
Чем может помочь jQuery вашему CSP(ZEN)-приложению?

1. Удобный доступ к любому элементу html-страницы

Фреймворк jQuery позволяет получать доступ к элементам c помощью синтаксиса селекторов CSS, реализуя этот функционал наиболее предпочтительным для каждого конкретного браузера способом.

2. Кроссбраузерность

Кроссбраузерность – неотъемлемое требование к современному Web-приложению. Разработчики фреймворка утверждают, что разработанный с помощью jQuery код, будет единообразно функционировать в браузерах – IE 6.0+, FF 3.6+, Safari 5.0+, Opera, Chrome.

Плагины

jQuery предоставляет удобный способ для расширения своей функциональности с помощью плагинов. С момента создания фреймворка (в 2006 г.) их было написано не мало. Отдельного упоминания заслуживает библиотека клиентского интерфейса jQueryUI.

Как подключить jQuery к вашей CSP (ZEN) странице?

Скачайте с официального сайта сжатую версию библиотеки, поместите ее в каталог CSP приложения, укажите на csp (zen) странице , что будет использоваться файл jQuery определенной версии.

Например:
CSP
<script type="text/javascript" src="jquery-1.7.1.min.js"></script>

ZEN
Parameter JSINCLUDES As STRING="jquery-1.7.1.min.js"

CLS
w "<script type='text/javascript' src='jquery-1.7.1.min.js'></script>"

CSP, ZEN и CLS – три подхода создания серверных страниц в Caché.
Каждая CSP-страница при компиляции превращается стандартный класс Cache – наследник класса %CSP.Page – CSP. Отсюда следует второй способ – кодоориентированный, предполагает изначальную разработку страницы с помощью классов Cache, наследников класса %CSP.Page – CLS подход. Подробнее о различиях смотрите здесь.
Третий способ – разработка с использованием Zen объединяет два этих способа – используются как Zen-теги так и объектный код. В приводимых примерах обозначения CSP, CLS, ZEN, обозначают способы разбработки страницы, который использовался при создании примера

Для интернет-приложений предпочтительным способом будет использование сетей доставки контента (CDN – content delivery network). В этом случае файл jQuery будет находиться на серверах одной из перечисленных сетей. Пример подключения с использованием CDN :
CSP:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>

ZEN:

Parameter JSINCLUDES As STRING="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js";

CLS:
&html<
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
>

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

Самая используемая функция jQuery так и называется – jQuery(), но чаще используется ее синоним – $(). Результат этой функции будет зависеть от типа и количества переданных аргументов. Основные варианты вызова этой функции:
1. В качестве аргумента передается строка:
Метод будет пытаться трактовать строку или как селектор jQuery (CSS) или как html код:

$("div#1973") // вернет элементы div с идентификатором 1973
$(".doc td") // вернет элементы <td> дочерние по отношению к элементам с классом doc
$("<div>Write less, do more</div>") // создаст* новый элемент

2. В качестве аргумента передается элемент страницы:

$( document.body ) // элемент body будет «упакован» в объект jQuery
$( document.forms[0] ) // элемент первая форма страницы будет «упакована» в объект jQuery

3. В качестве аргумента передается функция:

function loadHandler() { alert( "Документ загружен" ); }
$( loadHandler );

или анонимная функция:
$( function (){ alert( "Документ загружен" ); } );

Переданная функция будет выполнена по событию загрузки документа. Такой вызов нужно использовать вместо:

<body onload="loadHandler">

Кроме поиска элементов на странице, jQuery содержит методы для манипуляций с ними. Список часто используемых функций:

html() возвращает или устанавливает html-содержимое элемента.
val() возвращает или устанавливает значение элемента управления (input,select)
attr() возвращает или устанавливает значение атрибута элемента
append(), appendTo() добавляет элемент к другому, создает и добавляет элемент к другому
remove() удаляет элемент
bind(), unbind() Добавление, удаление обработчиков событий. Для основных событий вместо bind() можно использовать синонимы click(), keypress(), hover() и т.д.
addClass(), removeClass(), toggleClass()
добавляет, удаляет, переключает класс(ы) элемента
css() возвращает или устанавливает значения стиля
show(), hide() показывает, прячет элемент
parents(), parent() children() возвращает родительские или дочерние элементы

*Полный список функций можно посмотреть здесь

Необходимо отметить некоторые правила, реализованные в jQuery:

  • результат запроса упаковывается в объект jQuery;
  • большинство функций возвращают контекст вызова.

Соблюдение этих правил делает код «ненавязчивым» и позволяет использовать «цепочечный» синтаксис. Пример кода с использованием jQuery, выполняющий поиск элемента и замену его значения:

<script type="text/javascript" src="jquery-1.7.1.min.js"></script>
<script type=&quot;text/javascript">
$(function(){
   
   $( ".age38" ) // Найти любые элементы с классом age38
    .val( "Write less, do more" ) //их значение будет заменено на девиз
    .css( "font-size", "110%" ) // а также изменен размер шрифта
    .click( function(){ $(this).val("&"); } ) //при клике сбросим значение
    // Эту «цепочку» из функций можно продолжить дальше.

});
</script>

«Ненавязчивость» заключается в том, что для указанного выше кода, при отсутствии на странице элементов с классом age38, ошибки выполнения не возникает и выполнение кода не прерывается

Ниже показан пример на javascript с аналогичным функционалом, но без jQuery


<script type="text/javascript">
//объект в который будем складывать все свои переменные и функции
//чтобы не "засорять" пространство имен window
var page={}; // window.page=new Object();

// Функция отбора элементов страницы по их классу
// вернет массив элементов с таким классом
page.getByClass=(document.getElementsByClassName) ? function(clnm){

//В IE9, FireFox 3, Opera 9.5, Safari 3.1 есть специальный метод
return document.getElementsByClassName(clnm);

} : function(clnm){

///в IE 6.0-IE 8.0 такого метода нет
var arr=[]; /// сюда будем складывать найденные по классу элементы
/// Рекурсивный проход по дочерним элементам
var scanDown=function(obj,handler){
for (var child=obj.firstChild;child;child=child.nextSibling){
if (typeof(handler)=="function") handler(child);
scanDown(child,handler);
}
};
/// определим регулярное выражение для поиска класса
/// так как одному элементу может быть присвоено несколько классов
var rgxp=new RegExp("(\\s|^)" + clnm + "(\\s|$)");
/// Обработчик элементов
var classSelect=function( obj ){
if ( ( !obj ) || ( !obj.className )) return;
var cls=obj.className;
if (!cls.match( rgxp )) return;
arr[arr.length]=obj; ///нашли - запомнили
};
scanDown( document, classSelect ); //запускаем рекурсию
return arr;
}
/// Определим “универсальную” функцию привязки событий
page.bind=(page.ie)? function(obj,evt,func){
obj.attachEvent("on"+evt,func);
} : function(obj,evt,func){
obj.addEventListener(evt,func);
};
/// Основная функция обработки найденных элементов
page.main=function(obj){
if (!obj) return; if (obj.nodeType!=1) return; //проверки и еще раз проверки
obj.value="Write less, do more";
var FS="110%";
if (obj.style) {
obj.style.fontSize=FS;
} else {
obj.style="font-size:"+FS;
}
page.bind(obj,"click",function(){
obj.value="";
});
};
/// Запускаем после загрузки
page.bind( window, "load", function() {
var arr=page.getByClass("age38"); //найдем элементы
for (var i in arr){
page.main(arr[i]); //обработаем каждый из них
}
});
</script>

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

Cache + jQuery
Использование jQuery на CSP-странице

Рассмотрим небольшой пример реализации чата с минимальной функциональностью и следующим интерфейсом:
пример чата

Особенности примера:

  • интерфейс полностью формируется с помощью jQuery
  • используются асинхронные вызовы сервера #call()#, которые возвращают ответ через вызов функции на клиенте
  • данные хранятся в глобале

Исходный код страницы:

<!DOCTYPE html>
<html><head><title> jqChate </title>
<style type="text/css">.msg span {padding: 0px 0px 0px 5px}</style>
</head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript">
$( function(){
var USERNAME="#($USERNAME)#" //храним в переменной пользователя Cache
, box=$( "<div/>" ).css("overflow","auto") //создали окошко, добавили прокрутку
.css("border","1px solid #ccc").width( 340 ).height( 280 ) //формат
.appendTo( $(document.body) ) //и добавили в документ
;
box.parent() //перешли от окошка чата к body
//добавляем к body input, который сразу форматируем и подписываем
.append( $("<input />").css( "width",200 )
.attr("maxlength",50).before( USERNAME+": ") )
.append( //добавляем кнопку
$("<button/>").text( "Send" ) //к ней текст и обработчик
.click( function(){ //асинхронно передадим на сервер
#call(..Add($(this).prev().val()))#; //значение элемента перед кнопкой (input)
$("input").val("").focus(); //textbox очистим, и вернем ему фокус ввода
})
);
var drawMsg=function(/*[id,time,user,msg]*/ array ){ //вывод сообщения
var USER=2, MSG=3, SOFT="#999", BRIGHT="#339" //константы
, msg =
$( "<div><span>" + array.join("</span><span>") + "</span></div>") //html сообщения
.appendTo( box ).hide() //добавили и спрятали - вдруг служебное сообщение
.addClass("msg") //выставили класс
.children(":first").hide() //id - скрытое поле
.next().css("color", SOFT) //time
//подсветка пользователя
.next().css("color", (USERNAME==array[USER]) ? SOFT : BRIGHT ).append(": ")
.parent() //восстановили контекст и вернули в переменную msg
;
if (array[MSG]!=="") { // сообщение не служебное
msg.show(); //отображаем
var div=msg.get(0); //получаем доступ непосредственно к html-элементу
if (div) div.scrollIntoView(); //подкручиваем в область видимости
}
}; //drawMsg

window.chat=function(array){ //главная функция
if ( $.isArray(array) ) drawMsg( array ); //есть данные - выводим их
//ждем немного и спрашиваем сервер - какое следующее?
setTimeout(
//Последнее полученное ищем с помощью jQuery
function(){ #call(..GetNext($(".msg:last span:first").text()))# }
, #($get(^chatSettings("timeout"),300))#
); //настройки тоже на сервере
}; //window.chat

chat(); //запускаем цикл запрос-ответ
});
</script><body></body></html>

<script runat="server" method="Add" arguments="msg:%String" language="cache">
#; просто записываем данные
s ^chat($increment(^chat))=$listbuild( $zt( $now() ), $USERNAME, msg )
</script>

<script runat="server" method="GetNext" arguments="lastMsg:%String" language="cache">
#; разбираемся c переданными аргументами
set lastMsg=$get(lastMsg), prevMsg=$order( ^chat(lastMsg),-1 ), isNewUser=( lastMsg = "" )
set callBack="window.parent.chat(" ;
set isNewChat=( isNewUser && (prevMsg="") ) if ( isNewChat ) write callBack,")" Quit
if ( isNewUser ) write callBack,"[", prevMsg, ",'','',''])" Quit ;инициализация
set nextMsg=$order( ^chat( lastMsg ) ) if ( nextMsg="" ) write callBack,")" Quit
set stored=$get(^chat(nextMsg)) ;выводим сообщение
write callBack,"[",nextMsg,", ", ..QuoteJS( $listget(stored,1) )
, ",", ..QuoteJS( $listget(stored,2) )
, ",", ..QuoteJS( ..EscapeHTML( $listget(stored,3 )))
, "])"
</script>

Для воспроизведения примера, откройте Студию в любой области, создайте новую CSP-страницу, вставьте скопированный код страницы из примера. Сохраните страницу с любым именем – выберите при этом какое-либо существующее CSP-приложение. Затем откомпилируйте страницу (Ctrl+F7) и откройте ее в браузере ( F5).

Использование jQuery на Zen-странице

Применение jQuery на Zen странице полностью аналогично ипользованию в CSP (поиск и изменение элементов), но с учетом того, что Zen – это клиент-серверный фреймворк, который поддерживает одинаковую объектную модель в браузере и на сервере Cache. Поэтому необходимо учитывать следующие моменты:

  • Модель Zen-страницы начинает формироваться после события загрузки страницы.
  • Для изменения модели страницы предпочтительно использовать методы Zen
  • Zen формирует разметку с помощью дополнительных скрытых элементов

Практический пример Cache + jQuery
Приведем более сложный пример c использованием jQuery для создания и удаления объектов хранимого класса со следующим интерфейсом:
Особенности примера:

  • Пример работает на Cache версии 2010 и выше.
  • Используется динамическая привязка событий, весь javascript код вынесен в отдельный javascript файл
  • Часть содержимого страницы генерируется непосредственно в браузере
  • Не используются «гипер»-события и «динамические» (генерируемые на лету) метод-страницы
  • Сервер генерирует ответы в различных форматах данных в зависимости от аргументов запроса
  • Показан вывод серверного объекта в формате XML с помощью %XML.Writer и разбор ответа сервера в формате XML на стороне клиента с помощью jQuery

1. Создайте в области User хранимый класс с тестовыми данными
/// Класс с тестовыми данными
Class test.data Extends (%Persistent, %Populate, %XML.Adaptor) {
/// Обозначение
Property code As %String;
/// Наименование
Property name As %String;
/// Переопределенный "конструктор", рассчитанный на прием данных непосредственно со страницы
Method %OnNew(ByRef args as %String="") As %Status [ Private, ServerOnly = 1 ] {
s ..code=$g(args("code",1)), ..name=$g(args("name",1))
Quit $$$OK
}
}

2. Создайте тестовые данные в test.data, выполнив из терминала Cache вызов метода класса.
USER>d ##class(test.data).Populate()

3. Создайте класс-страницу:

/// Страница работы с экземплярами класса test.data
Class wui.testData Extends %CSP.Page {

/// Рекомендуемое значение
Parameter CHARSET="utf-8";

/// Переопределим метод вывода страницы
ClassMethod OnPage() As %Status {

#; Эта страница обрабатывает три типа запросов
#; 1.просто вывод данных
#; 2.запрос на создание объекта
#; 3.запрос на удаление объекта
#; Тип запроса различаются по значению аргумента <var>oper</var>

m args=%request.Data ;Заберем все данные из запроса

if $d(args("oper")){ //если определен аргумент oper
s oper=$g(args("oper",1)) //узнаем тип запроса
Q:oper="add" ..Add(.args) //перейдем к созданию объекта в методе этого класса
Q:oper="del" ..Del(.args) //перейдем к удалению объекта
}

#; в остальных случаях просто выводим страницу
&html<<html><head><title>Класс test.data</title>

<!-- Часть оформления вынесем в CSS -->
<style type="text/css">
table {border-collapse: collapse; border: 1px solid #777;}
td, th {border: 1px solid #777;}
td {padding: 2px;}
th {font-weight: normal; color:#fff;background-color:#aaa;}
</style>
</head>
<body>
<table><caption>Объекты класса test.data</caption>
<thead><tr><th>ID</th><th>code</th><th>name</th><th><!--Кнопка--></th></tr></thead>
<tbody>>

#; Выведем имеющиеся объекты в виде таблицы
s sql="Select ID,code,name From test.data"
s rs=##class(%SQL.Statement).%ExecDirect(.stm, sql) ; У вас версия Cache > 2009 ?
while rs.%Next() {
w !,"<tr id='",rs.ID,"'>"
, "<th>",rs.ID,"</th>" ;порядковый номер строки
, "<td>",rs.code,"</td>"
, "<td>",rs.name,"</td>"
, "</tr>"
}

&html<</tbody></table>
<!--подключаем jQuery-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<!—весь клиентский код вынесем в отдельный файл-->
<script src="wui.testData.js"></script>
</body></html>>
Quit $$$OK
}

///Добавление объекта на стороне сервера и ответ клиенту в формате XML
ClassMethod Add(ByRef args as %String="") as %Status{

#; выполним добавление нового объекта
s obj=##class(test.data).%New(.args), sc=obj.%Save()

#; выведем результат добавления и сам объект в виде xml
s wr=##class(%XML.Writer).%New() ;начнем вывод xml

d wr.RootElement("response") ; <response
, wr.WriteAttribute("id",obj.%Id()) ; id=''

#;новый объект в виде xml
d wr.Object(obj) ; ><data><code>123</code><name>Name</name></data>
if $$$ISERR(sc) {
d wr.Element("error") ;<error>
d wr.WriteChars($system.Status.GetOneErrorText(sc)) ;errorText
d wr.EndElement() ;</error>
}
d wr.EndRootElement() ;</response>

Q $$$OK
}

///Удаление объекта на стороне сервера и ответ в виде обычного текста
ClassMethod Del(ByRef args as %String="") as %Status{
s id=$g(args("id",1)), sc=##class(test.data).%DeleteId(id)
w $CASE(sc
,1: 1
, : $system.Status.GetOneErrorText(sc)
)
Q $$$OK
}
}

Далее приведен Javascript код, вынесенный в файл wui.testData.js. Файл должен находиться в каталоге csp-приложения. Для области USER каталог находится в [Директория установки Cache]\CSP\USER\.

$(function(){ //обработчик document.ready

//Добавим к таблице интерфейс для создания объектов
var footer=[ //используется один из вариантов объявления массива
"<tfoot><tr>"
,"<th>*</th>" //колонка "порядковый номер"
,"<th><input /></th>" //колонка "код"
,"<th><input /></th>" //колонка "наименование"
,"<th><button> + </button></th>" //кнопка сохранения новой записи
,"</tr></tfoot>"
].join( "" ); //Объединим в строку (оптимизация работы со строкой)

//Создадим элемент с таким содержимым
$( footer ).appendTo( $( "table" ) ); //И добавим его к таблице

//доберемся до только что созданной кнопки добавления записи
$( "tfoot button" ).click( callAdd ); //и привяжем к ней обработчик

//к каждой tr из tbody (строки с данными test.Data) добавим кнопку удаления
$("tbody tr").each( addDelBtn ); //создание кнопок удаления

//Функция добавления кнопки удаления к строке с данными
function addDelBtn (){ //для каждой строки данных создадим кнопки удаления

var $tr=$(this); //упакуем контекст (tr из tbody) в jQuery
$( "<th><button class='bDel'></button></th>" ) //создание элемента
.appendTo( $tr ) //добавим в конец строки ячейку с кнопкой удаления
.children( ".bDel" ) //для дочерних элементов c классом bDel
.html( "X" ) //Заменить содержимое кнопки
.attr( "title", "Удалить запись" ) // добавить подсказку
.css( "color", "#f00" ) //изменить цвет надписи на предупреждающий
.click( callDel ) //при клике на кнопку вызываем удаление данных
;
};

// Функция удаления данных на клиенте и сервере
function callDel(){
//контекст вызова - кнопка удаления
var $btnDel=$( this ).hide(); //спрячем кнопку и сохраним ссылку на нее
var $row=$button.parents( "tr" ); //ищем родительский элемент tr
var id=$row.attr( "id" ); //узнаем код объекта

// Отправим запрос на сохранение данных с помощью
// функции $.ajax- http://api.jquery.com/jQuery.ajax/
$.ajax({ //ajax options
url: window.location //вызов адреса страницы - wui.testData.cls
,data: "oper=del&id="+id //параметры запроса
,dataType: "text" //ожидаем ответ в формате text
,success: function(txt){ //при успешном выполнении запроса
if ( txt!="1" ) { //при удалении произошла ошибка
$btnDel.show(); //вернем видимость кнопке удаления
alert(txt); //покажем ошибку
return;
}
$row.remove(); //удалим строку на клиенте
}
}); //ajax()
};

// Функция создания данных чуть сложнее
function callAdd(){

var values = [] // в этот массив соберем значения для новой записи
, $inputs = $( "tfoot input" ) //найдем элементы ввода
//и для каждого из них
.each( function(){
values.push( //в массив значений поместим
$( this ).val() //значение input
)
} //function
) //each
; //var

//проверим, что введены хоть какие-нибудь данные
if ( values.join( "" ) == "" ){
alert( "Укажите хотя бы одно значение" ); return;
};

//до выполнения операции на сервере прячем интерфейс добавления
var $addRow = $( "tfoot tr" ).hide();

// Отправим запрос на сохранение данных с помощью функции
// $.ajax- http://api.jquery.com/jQuery.ajax/
$.ajax( { //настройки вызова сервера
url: window.location //адрес - wui.testData.cls
//,async: true // по умолчанию запрос выполняется асинхронно
, type: "POST" // тип запроса - POST или GET
, data: { //передаваемые на сервер данные в виде объекта
oper: "add" //операция добавления
, code: values[0] //значение кода
, name: values[1] //значение наименования
}
, dataType: 'text' //обработаем ответ сервера как строку.

//если вызов завершен успешно
, success: function( text ){

// ожидаем c сервера текст в XML следующего вида
// <response id='newid'>
// <data><code>Code</code><name>Name</name></data>
// <error>Error Text</error>
// </response>
// разбираем документ с помощью функции jQuery
var xml=$.parseXML(text)
, $xml=$( xml ) //оборачиваем его в jQuery
, error=$xml
.find("error") //ищем node с ошибкой
.text() //получаем текстовое значение
;
if ( error!=="" ){ //если была
alert(error); //вывод ошибки
return;
}
//Разбор ответа на составляющие элементы
var id=$xml.find("response").attr("id")
, code=$xml.find("code").text()
, name=$xml.find("name").text();

var tr=[ //сформируем строку с полученными данными
"<tr id='",id,"'>"
, "<th>",id,"</th>"
, "<td>",code,"</td>"
, "<td>",name,"</td>"
,"</tr>"
].join( "" );

$( tr )
.appendTo( $("table tbody") ) //добавим строку в таблицу
//для новой строки вызовем функцию добавим кнопку удаления
.each( addDelBtn )
;
$inputs.val( "" ) //очистим элементы ввода
.focus() //установим фокус
;

} //success

, error: function(err){ //если вызов завершен с ошибкой
alert("Error: "+err);
}

//обработчик события, возникающего после success и error
, complete: function(){
$addRow.show(); //покажем строку добавления объекта
}

} //настройки
); //$.ajax
}; //callAdd

}); //document.ready

Некоторые итоги:

  • Использование библиотеки jQuery значительно упрощает разработку web-интерфейса;
  • jQuery позволяет удобно разделить динамическое и статическое содержимое страницы;
  • в jQuery несложно вынести большую часть логики интерфейса на сторону браузера, тем самым снизив нагрузку на сервер Caché.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *