В статье показывается способ применения jQuery для работы с базой данных Caché, в результате чего реализуется простой функционал по вставке и удалению данных в ajax веб-приложении.
Предполагается, что читатель обладает средним уровнем знаний HTML, CSS и Javasсript и имеет минимальный опыт работы с Intersystems Cache. Загрузить последнюю версию Caché можно здесь. Начальный опыт работы с Caché можно обрести тут.
Автор статьи читатель doublefint — у него недостаточно кармы для публикации статьи в этот блог, поэтому я делаю публикацию от своего имени, но надеюсь карма у него появится и он сможет комментировать вопросы к статье лично.
Зачем нужен 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="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> jqChaté </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>
Для воспроизведения примера, откройте Студию в любой области, создайте новую 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. Наполните класс объектами, выполнив из терминала 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]CSPUSER.
$(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é.
Автор: morisson