Крейн Д., Паскарелло Э., Джеймс Д,. AJAX в действии / Ajax в действии / ajinact / Ajax в действии
.pdf258 Часть III Создание профессиональных Ajax-приложений
// Скрыть диалоговое окно m s g . h i d e D i a l o g = f u n c t i o n ( e ) {
va r d i a l o g s ( t h i s . d i a l o g ) ? t h i s . d i a l o g : m s g . d i a l o g ; i f ( d i a l o g ) {
if (dialog.modalLayer){ dialog.modalLayer.style.display='none' ;
}else{ dialog.style.display='none' ;
} |
|
|
} |
|
|
} |
|
|
// |
Отобразить диалоговое окно |
|
msg.showDialog=function(e){ |
||
var dialog=(this.dialog) ? this.dialog : msg,dialog; |
||
|
if |
(dialog){ |
|
if |
(dialog.modalLayer){ |
|
dialog.modalLayer.style.display*1block1 ; |
|
|
}else{ |
|
|
dialog.style.display='block'; |
|
} |
|
|
} |
|
|
} |
m |
К функции render () можно обращаться многократно. В процессе работы она проверяет присутствие компонентов пользовательского интерфейса О, © и по мере необходимости создает их, используя функции createDialog © и createBar ©. Для формирования компонентов интерфейса используются стандартные методы обработки элементов DOM. Обработчики событий позволяют отображать и скрывать диалоговое окно.
Для воспроизведения всех сообщений система сначала сортирует их по приоритету и размещает в трех временных массивах @. Затем низкоприоритетные сообщения выводятся в строке состояния ©, а остальные — в диалоговом окне, причем первыми располагаются высокоприоритетные сообщения О.
Для того чтобы реализовать модальное окно, мы помещаем видимые средства поддержки диалога в элемент DIV, занимающий весь экран, и блокирующий все события, связанные с мышью ©. Для модального элемента DIV определен белый фон с прозрачными пикселями. По этому признаку можно отличить модальное окно. Мы не используем установки прозрачности CSS, потому что в этом случае любой вложенный элемент будет также прозрачным. Файл CSS для системы оповещения представлен в листинге 6.7.
Листинг 6.7. Содержимое файла msg. ess
•msg_small_icon{ height: 32px; width: 32px; position:relative; float:left;
}
• ™sg_jiialog_icon { height: 32px; width: 32px;
Глава 6. Информация для пользователя 259
p o s i t i o n : r e l a t i v e ; f l o a t : r i g h t ;
1
• msg _ large _ icon { h e i g h t : 64px;
w i d t h : 64px;
}
.msg_text{
f o n t - f a m i l y : a r i a l ; f o n t - w e i g h t : l i g h t ; f o n t - s i z e : 14pt;
c o l o r : b l u e ;
}
,msgbar{
position:relative; background-color: white; border: solid blue lpx; width: 100%;
height: 38px; padding: 2px;
}
.dialog{
position: absolute; background-color: whiteborder: solid blue lpx; width: 420px;
top: 64px; left: 64px; padding: 4px;
}
.popup{
position; absolute; background-color: white; border: solid blue lpx; padding: 4px;
}
.non-modal{
}
.modal{
position: absolute; top: Opx;
left: Opx; width: 100%; height: 100%;
background-image:url(img/modal_overlay.gif);
260 Часть III. Создание профессиональных Ajax-приложений
Рис. 6.5. Использование CSS-атрибутов f l o a t позволяет размещать пиктограммы в контейнере любой формы.
В данном примере мы задали квадратную строку состояния; пиктограммы автоматически перекомпоновались с учетом выравнивания по левому и правому краю
Следует заметить, что в GSb-классах msg_small_icon и msg_dialog_icon используется стиль float. Класс msg_small_icon применяется для воспроизведения пиктограмм, соответствующих низкоприоритетным сообщениям. Эти пиктограммы располагаются в ряд, начиная с левого края. Для msg__dialog_icon задано выравнивание по правому краю. Базовые средства позволяют отображать строку состояния любой формы и любого размера. Плавающие элементы располагаются в строке состояния в ряд; при заполнении текущего ряда формируется следующий ряд (рис. 6.5).
Теперь, когда мы создали пользовательский интерфейс для объекта Message, надо модифицировать сам объект. Создаваемые сообщения должны иметь возможность включать самих себя в состав пользовательского интерфейса и удаляться по окончании действия. В листинге 6.8 показаны изменения, которые необходимо внести в состав кода, чтобы обеспечить эти возможности.
Листинг 6.8. Модифицированный объект Message
var msg=new Object();
msg.PRIORITY_LOWs= { id: 1, lifetime: 30, icon: "img/msg_lo.png" } ; msg.PRIORITY_DEFAULT={ id:2, lifetime:60, icon:"img/msg_def.png" J; msg.PRIORITY_HIGH= { i d : 3 , lifetime:- l, icon: "img/msg__hi .png" } ; msg.messages=new ArrayO;
msg.dialog=null;
msg.msgBarDiv=null;
msg.suppressRender=false;
msg.Message=function(id,message,priority,lifetime,icon){
this.id=id;
msg.messages[id]=this;
this.message=message;
this.priority={priority) ? priority : msg. PRIORITY__DEFAULT. id; this.lifetime=(lifetime) ? lifetime : this.defaultLifetime(); this.icon={icon) ? icon : this.defaultlcon();
if (this.lifetime>0){ this.fader=setTimeout( "msg.messages['"+this.id+"'].clear()", this.lifetime*1000
);
}
// О Дополнительные параметры if (!msg.suppressRender){ this.attachToBar();
Глава 6. Информация для пользователя 261
}
}
// @ Дополнительные параметры msg.Message.prototype.attachToBar=function(){
if (!msg.msgbarDiv){ msg.render();
}else if (this.priority==msg.PRIORITY_LOW.id){ this.render(msg.msgbarDiv);
lelsei
if (!msg.dialog){ msg.dialog=msg.createDialog( msg.msgbarDiv.id+"_dialog", msg.msgbarDiv, (this.priority==msg.PRIORITY_HIGH.id) );
}
this.render(msg.dialog.tbod);
msg.showDialog();
}
}
msg.Message.prototype.clear=function(){
msg.messages[this.id]=null;
// & Дополнительные параметры if (this.row){ this.row.style.display='none' ; this. row.messageObj'^nullfthis.row^null;
}
i f ( t h i s . i c o T d ) {
t h i s . i c o T d . s t y l e . d i s p l a y s ' n o n e ' ; t h i s . i c o T d . m e s s a g e O b j = n u l l ;
t h i s . i c o T d = n u l l ;
}
_ 1 |
я |
Мы стремимся упростить работы с системой, поэтому при создании сообщения оно автоматически включается в состав пользовательского интерфейса О. Для того чтобы обеспечить воспроизведение нового сообщения, достаточно вызвать конструктор объекта. В зависимости от приоритета сообщения оно будет помещено либо в строку состояния, либо в диалоговое окно ©. Если нежелательно воспроизводить каждое сообщение, например, когда мы добавляем одновременно несколько сообщений, предусмотрен флаг, позволяющий отменить автоматическое отображение. В этих случаях после создания всех необходимых сообщений мы можем вручную вызвать функцию msg.render().
Удаляя сообщения с помощью функции clear(), мы автоматически удаляем соответствующие элементы пользовательского интерфейса ©.
Итак, мы получили комплект базовых средств, упрощающих оповещение пользователей. Мы можем включать оповещение вручную или применять созданную систему в составе других компонентов, предназначенных для по-
262 Часть III Создание профессиональных Ajax-приложений
вторного использования. В следующем разделе будет продемонстрирована совместная работа системы оповещения с объектом ContentLoader Пользователю будет предоставляться информация о загрузке объектов по сети.
6,5. Предоставление информации в запросах
В главе 5 мы представили объект ContentLoader, инкапсулирующий сетевой трафик. Применим систему оповещения для информирования пользователя о состоянии запроса. Начнем с формулирования общих требований.
При передаче запроса серверу мы должны сформировать низкоприоритетное сообщение о том, что данный запрос обрабатывается. Для того чтобы отделить запрос по сети от других оповещений с низким приоритетом, мы изменим внешний вид пиктограммы. Будем использовать для представления сообщения данного типа условное изображение земного шара. Оно присутствовало в программе просмотра информации о планетах, которая рассматривалась в главах 4 и 5.
По окончании обработки запроса сообщение должно быть удалено. Вместо него следует создать другое низкоприоритетное сообщение о том, что запрос обработан успешно, либо сообщение со средним приоритетом о возникновении ошибки.
Для того чтобы реализовать подобное поведение, нам надо создавать объекты Message на определенных этапах жизненного цикла запроса, а именно: при его инициализации, а также по завершении или получении информации об ошибках. Модифицированный код объекта ContentLoader показан в листинге 6.9.
Листинг б.Э.-Объект ContentLoader с оповещением пользователя net.ContentLoader=function( ... )
{ ... } ;
net.ContentLoader.msgld=l;
net.ContentLoader.prototyped
loadXMLDoc:function{url,method,params,contentType){ if {Imethod){
method="GET";
}
if {IcontentType && method=="POST"){
contentType='application/x-www-form-urlencoded' ;
I
if (window.XMLHttpRequest){ this.req=new XMLHttpRequest();
} else if (window.ActiveXObject){
this.req=new ActiveXObj ect("Microsoft.XMLHTTP");
I
if (this.reqH try{
var loader=this; this.req.onreadystatechange=function(){ loader.onReadyState.call(loader);
)
Глава 6 Информация для пользователя 263
t h i s . r e q . o p e n ( m e t h o d , u r l , t r u e ) ; i f ( c o n t e n t T y p e ) I
t h i s . r e q . s e t R e q u e s t H e a d e r ( ' C o n t e n t - T y p e ' , c o n t e n t T y p e ) ;
}
// О Оповещение об инициализации запроса this.notification=new msg.Message( "netOO"+net.ContentLoader.msgld, "loading "+url,
msg.PRIORITY_LOW.id, -1, "img/ball-earth.gif" );
net.ContentLoader.msgld++;
this.req.send(params); }catch (err){ this.onerror.call(this);
}
}
Ь
onReadyState:function(){ var req=this.req;
var ready=req.readyState;
if (ready==net.READY_STATE__COMPLETE) { var httpStatus=req.status;
if (httpStatus==200 || httpStatus==*O) { this.onload.call(this);
// @ Удаление существующего оповещения this.notification.clear{);
}else{
this.onerror.call(this);
}
}
},
// © Сообщение об ошибке defaultError:function(){
var msgTxt="error fetching data!"
+ "<ulxli>readyState: "+this. req.readyState +"<li>status: "+this.req.status
+"<li>headers: "+this.req.getAHResponseHeaders() +"</ul>";
if (this.notification){ this.notification.clear();
J
this.notifxcation=new msg.Message(
"net__errOO"+net. ContentLoader .msgld, msgTxt,msg.PRIORITY__DEFAULT.id
);
net.ContentLoader.msgld++;
}
> ^ |
• |
264 Часть III Создание профессиональных Ajax-приложений
Когда мы создаем запрос посредством вызова loadXMLDoct), мы формируем оповещение О и связываем ссылку на него с объектом ContentLoader.
Заметьте, что время жизни |
устанавливается равным -1, поэтому сообщение |
не будет автоматически удаляться. |
|
В методе onReadyState() |
мы удаляем сообщение при благополучном за- |
вершении операции €). При |
наличии ошибки мы вызываем метод defaultEr - |
r o r (), который формирует соответствующее сообщение €>. Чтобы сообщение лучше воспринималось пользователями, вместо обычного текста для его формирования применяются средства HTML.
В листинге 6.10 показан пример страницы, в которой используется модифицированный объект ContentLoader
Листинг 6.10. Страница, предусматривающая оповещение пользователя
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtmll/DTD/xhtmll-strict.dtd"> <btml>
<head>
<title>Notifications test<Vtitle>
<link rel=stylesheet type="text/css" href="msg.ess"/> <script type="text/javascript" src="x/x_core.js"X/script>
<script type="text/javascript" src="extras-array.js"x/script> <script type="text/javascript" src="styling.js"></script> <script type="text/javascript" src="msg.js"></script>
<script type="text/javascript" src="net_notify.js"X/script> <script type="text/javascript">
window.onload=function(){
msg.render('msgbar');
}
var msgld=l;
function submitUrl(){
var url=document,getElementById{'urlbar').value; // О Запрос к. серверу
var loader=new net.ContentLoader(url,notifyLoaded);
}
function notifyLoadedf){
// © Оповещение о том, что ресурс загружен var doneMsg=new msg.Message( "done00"+msgld,
"loaded that resource you asked for: "+this.url, msg.PRIORITY_LOW.id
);
msgld++; msg.render('msgbar1);
}
</script>
</head>
<body>
<div class='content'>
<p>here is some content. This is what the web
Глава 6 Информация для пользователя 265
рис. 6.6. В окне подсказки, которое отображается при помещении курсора на пиктограмму, содержатся сведения об успешной загрузке ресурса
Рис 6.7. Если при обработке запроса возникла ошибка, информация о ней отображается в диалоговом окне (в данном случае в окне отображены два сообщения; второе информирует об ошибке)
application is up to when not being bugged silly by all these messages and notifications and stuff. <p>Type in a URL in the box below (from the
same domain, see Chapter 7), and hit 'submit'. A souped-up contentloader that understands the notification system will be invoked.
<input id='urlbar' type='text1/>
<a href='javascript:submitUrl()'>submit</a>
</div> |
|
|
|
<div id='msgbar' |
class='msgbar'></div> |
||
</body> |
|
|
|
</html> |
|
m |
|
|
|
|
Web-страница (рис. 6.6 и 6.7) содержит простую HTML-форму, в которой пользователь может ввести URL. По щелчку на ссылке submit предпринимается попытка загрузить ресурс, на который указывает URL О. В случае успешного завершения операции происходит обращение к функции обратного вызова — notifyLoaded(). Функция notifyLoaded() не выполняет с ресурсом никаких действий, а лишь сообщает о его загрузке, создавая объект Message ©.
Заметьте, что поведение в случае успешной обработки запроса не предусмотрено при реализации системы. Оно обеспечивается посредством обработчика onload. Благодаря такому решению система может быть адаптирована с учетом различных требований. В примере, приведенном в листинге 6.9, поведение в случае ошибки было реализовано непосредственно в коде. В ре-
266 Часть III. Создание профессиональных Ajax-приложений
альных приложениях не каждая ошибка, связанная с передачей данных по сети, настолько важна, чтобы отображать сообщение о ней в диалоговом окне. Мы предлагаем читателю в качестве упражнения самостоятельно добавить к ContentLoader параметр, определяющий приоритет сообщения об ошибке (либо другим способом изменить обработчик onError для реализации менее строгой политики).
Итак, мы продемонстрировали, как можно обеспечить взаимодействие разработанной нами системы оповещения с существующим кодом. В следующем разделе мы рассмотрим альтернативные соглашения об оповещении
иприведем пример их применения.
6.6.Информация о новизне данных
Система оповещения, созданная нами, предоставляет набор компонентов верхнего уровня, отображающих информацию о системной активности. В некоторых случаях пользователю требуются дополнительные сведения, например, о том, что некоторые данные были модифицированы. В этом разделе мы модифицируем объект ObjectViewer, который рассматривался в главах 4 и 5, так, чтобы он сообщал пользователю информацию о данных, недавно подвергшихся изменению.
6.6.1. Простой способ выделения данных
Начнем решение задачи с рассмотрения простого способа выделения данных путем подсветки; для этой цели будем использовать инвертирование изображения. В пользовательском интерфейсе ObjectViewer используются в основном синие и серые тона, поэтому красный цвет будет выделяться на фоне окружающих элементов. Первое, что нам нужно сделать, — это определить дополнительный класс CSS, представляющий недавно измененные данные.
.new{ background-color: IfOeOdO; }
Мы выбрали очень простой стиль. Для реального приложения этого недостаточно; чтобы интерфейс выглядел хорошо, надо приложить дополнительные усилия. В листинге 6.11 показаны изменения кода ObjectViewer, предназначенные для того, чтобы выделить свойства, недавно подвергшиеся редактированию. В данном случае признак новизны данных автоматически удаляется по истечении заданного интервала времени.
Листинг 6.11. ObjectViewer, предусматривающий выделение новых данных'' objviewer,PropertyViewer.prototype.commitEdit=function(value){
if (this.type=objviewer.TYPE_SIMPLE){ this.value=value;
var valDiv=this.renderSimple(); var td=this.valTd;
td.replaceChild(valDiv,td.firstChild);
this.viewer.notifyChange(this);
Глава 6. Информация для пользователя |
267 |
// О Установить признак новизны this.setStatus(objviewer.STATUS_NEW);
}
}
objviewer.STATUS_NORMAL=1; obj viewer.STATUS_NEW=2;
objviewer.PropertyViewer.prototype.setStatus=function(status){
this.status=status; if (this.fader){
clearTimeout(this.fader);
}
if (status==objviewer.STATUS_NORMAL){ this.valTd.className='objViewValue' ; }else if (status==objviewer.STATUS_NEW){ this.valTd.className='objViewValue new1 ; var rnd="fade_"+new Date(),getTime(); this . valTd . id=rnd;
this . valTd . fadee=this;
// 0 Установить значение тайы-аута
t h i s . f a d e r = s e t T i m e o u t ( " o b j v i e w e r . a g e ( ' " + r n d + " 1 ) " , 5 0 0 0 ) ;
}
}
o b j v i e w e r . a g e = f u n c t i o n ( i d ) {
v a r e l = d o c u m e n t . g e t E l e m e n t B y I d ( i d ) ; v a r v i e w e r = e l . f a d e e ;
// © Восстановить состояние по окончании отсчета времени viewer.setStatus(objviewer.STATUS_NORMAL);
e l . i d - " " ;
e l . f a d e e = n u l l ;
_} |
m |
|
|
|
Мы определили два варианта данных: "обычные" и "новые". Можно было бы использовать для их идентификации логическое значение isNew, но мы предпочли определить два признака. Такой подход допускает дальнейшее расширение; например, мы сможем ввести дополнительный признак для тех данных, которые были модифицированы и информация об обновлении которых передана на сервер. При фиксации изменяемых значений вызывается метод setStatus () О. В результате задается соответствующий класс CSS и для "новых" данных устанавливается значение таймера ©. После истечения пятисекундного интервала данные переходят в "нормальное" состояние. (Для реального приложения время, в течение которого информация будет отображаться как "новая", должно быть намного больше. Интервал в пять секунд выбран только для проверки и демонстрации возможностей данного решения.) Объект сохраняет ссылку на таймер и может переустановить его значение в случае, если до истечения заданного времени возникнут новые изменения.