forked from DiscoverMeteor/DiscoverMeteor_Ru
-
Notifications
You must be signed in to change notification settings - Fork 0
/
09-errors.md.erb
221 lines (159 loc) · 14.7 KB
/
09-errors.md.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
---
title: Ошибки
slug: errors
date: 0009/01/01
number: 9
contents: Создадите лучший механизм для отображения ошибок и сообщений.|Узнаете как использовать `Template.rendered`, чтобы узнать когда пользователь увидел ошибку.|Используете фильтр для роутера, чтобы ошибки отображались только один раз.
paragraphs: 31
---
Использование стандартного диалога `alert()` для уведомления пользователя об ошибках и проблемах вряд ли оставит о нашем приложении хорошее впечатление. Мы можем сделать все гораздо лучше.
Давайте создадим универсальный механизм оповещения об ошибках, который будет уведомлять пользователей не слишком отвлекая их от приложения.
### Представляем Локальные Коллекции (Local Collections)
Мы создадим простую систему, которая будет следить за тем, какие ошибки пользователь уже успел просмотреть, а также показывать новые ошибки в специально отведенной области на сайте под названием "flash".
Наша система будет похожа на сообщения ошибок в Ruby on Rails, но только более утонченную - она будет находиться на клиенте и знать, когда пользователь уже просмотрел сообщение.
Для начала, мы создадим коллекцию для хранения ошибок. Учитывая то, что ошибки относятся только к текущей сессии и их вовсе не нужно сохранять на будущее, мы сделаем что-то новенькое. Мы создадим _локальную коллекцию_. Это означает, что коллекция `Errors` будет существовать только в браузере и не будет даже пытаться синхронизироваться с сервером.
Для этого мы создадим объект ошибки в файле, доступном только на клиенте, с именем коллекции `null`. Мы создадим функцию `throwError` которая будет добавлять новую ошибку в нашу локальную коллекцию:
~~~js
// Локальная коллекция, доступна только на клиенте
Errors = new Meteor.Collection(null);
~~~
<%= caption "client/helpers/errors.js" %>
Теперь, когда коллекция создана, мы можем добавить функцию `throwError`, которую мы будем вызывать для добавления новых ошибок. Нам не нужно заботиться об обработчиках `allow` или `deny`, так как это локальная коллекция, которая не будет сохранена в базу данных Mongo.
~~~js
throwError = function(message) {
Errors.insert({message: message})
}
~~~
<%= caption "client/helpers/errors.js" %>
Преимущество локальной коллекции для сохранения ошибок в том, что как и все коллекции она реактивна. Это позволит нам оперативно отражать ошибки в пользовательском интерфейсе, точно также как и любую другую коллекцию.
### Выводим ошибки
Ошибки будут выводиться вверху нашего главного шаблона:
~~~html
<template name="layout">
<div class="container">
{{> header}}
{{> errors}}
<div id="main" class="row-fluid">
{{yield}}
</div>
</div>
</template>
~~~
<%= caption "client/views/application/layout.html" %>
<%= highlight "7" %>
Давайте создадим шаблоны `errors` и `error` в файле `errors.html`:
~~~html
<template name="errors">
<div class="errors row-fluid">
{{#each errors}}
{{> error}}
{{/each}}
</div>
</template>
<template name="error">
<div class="alert alert-error">
<button type="button" class="close" data-dismiss="alert">×</button>
{{message}}
</div>
</template>
~~~
<%= caption "client/views/includes/errors.html" %>
<% note do %>
### Шаблоны близнецы
Вы наверное обратили внимание, что мы поместили два шаблона в один файл. До последнего момента мы придерживались правила "по одному шаблону в один файл", но для Meteor все будет работать даже если мы поместим все шаблоны в один файл (хотя при этом наш `main.html` стал бы плохо читаем и огромен в размерах).
В нашем случае оба шаблона довольно кратки. Мы сделаем исключение и поместим оба шаблона в один файл чтобы не засорять наш репозиторий обилием файлов.
<% end %>
Нам осталось только добавить логику поиска ошибок в нашем методе шаблона (helper):
~~~js
Template.errors.helpers({
errors: function() {
return Errors.find();
}
});
~~~
<%= caption "client/views/includes/errors.js" %>
<%= commit "9-1", "Простой вывод ошибок." %>
### Создаем ошибки
Мы знаем как выводить ошибки, но нам все еще нужно создать некоторое количество ошибок чтобы что-то заработало. Обычно ошибки появляются в процессе ввода пользователями новой информации. Сейчас мы добавим проверку в функции-обработчике события создания нового поста, и выведем сообщение для каждой ошибки.
Вдобавок, если у нас будет ошибка `302` (означающая что пост с данным URL уже существует), мы перенаправим пользователя на страницу с существующим постом. Мы получим `_id` этого поста из `error.details` (если вы помните из главы 7, мы передаем `_id` этого поста как третий аргумент `details` нашего класса `Error`).
~~~js
Template.postSubmit.events({
'submit form': function(e) {
e.preventDefault();
var post = {
url: $(e.target).find('[name=url]').val(),
title: $(e.target).find('[name=title]').val(),
message: $(e.target).find('[name=message]').val()
}
Meteor.call('post', post, function(error, id) {
if (error) {
// показываем ошибку пользователю
throwError(error.reason);
if (error.error === 302)
Router.go('postPage', {_id: error.details})
} else {
Router.go('postPage', {_id: id});
}
});
}
});
~~~
<%= caption "client/views/posts/post_submit.js" %>
<%= highlight "12~14, 16~21" %>
<%= commit "9-2", "На самом деле используем систему вывода ошибок." %>
Попробуйте создать пост и ввести URL `http://meteor.com`. Так как этот URL уже добавлен к посту из тестовых данных, вы скорее всего увидите:
<%= screenshot "9-1", "Преднамеренно вызываем ошибку" %>
### Убираем ошибки
Если вы уже попробовали клинуть на кноке закрытия ошибки, вы заметили что ошибка исчезает. Но если вы попробуете открыть другую страницу, ошибка вновь появится. Что же происходит?
Кнопка закрытия ошибки вызывает скрипт Twitter Bootstrap, и не имеет ничего общего с Meteor! Происходит следующее - Bootstrap удаляет `<div>` с сообщением об ошибке из DOM, но не из коллекции Meteor. Само собой, ошибка тут же появится вновь, как только Meteor отрисует страницу заново.
Если мы не хотим, чтобы ошибки восставали из мертвых, напоминая пользователям об их прошлых проступках и сводя их с ума, нам стоит добавить механизм удаления ошибок из коллекции.
Для начала мы изменим функцию `throwError`, добавив в нее свойство `seen`. Это нам пригодится чуть позже для ведения учета, видел ли пользователь уже эту ошибку.
Затем мы создадим простую фукцию `clearErrors` очищающую коллекцию от уже отображенных ошибок:
~~~js
// Local (client-only) collection
Errors = new Meteor.Collection(null);
throwError = function(message) {
Errors.insert({message: message, seen: false})
}
clearErrors = function() {
Errors.remove({seen: true});
}
~~~
<%= caption "client/helpers/errors.js" %>
<%= highlight "5,8~10" %>
Дальше, мы добавим очистку от ошибок в роутер, чтобы при открытии новой страницы старые ошибки удалялись автоматически:
~~~js
// ...
Router.before(requireLogin, {only: 'postSubmit'})
Router.before(function() { clearErrors() });
~~~
<%= caption "lib/router.js" %>
<%= highlight "4" %>
Чтобы наша функция `clearErrors()` заработала, ошибки должны помечаться параметром `seen`. Для этого стоит учесть один особенный случай. Когда мы выдаем ошибку и перенаправляем пользователя на другую страницу (как в случае с уже существующей ссылкой), перенаправление происходит мгновенно. У пользователя не остается времени увидеть ошибку перед тем, как она будет убрана с экрана.
Вот тут мы и воспользуемся параметром `seen`. Нам нужно установить его значение в `true` если пользователь успел увидеть ошибку.
Для этого мы воспользуемся `Meteor.defer()`. Эта функция сообщает Meteor выполнить коллбек сразу же после того, что происходит сейчас. Вы можете представить себе вызов `defer()` как способ сообщить браузеру подождать 1 миллисекунду, перед тем как двигаться дальше.
Мы просим Meteor установить параметр `seen` в `true` одной миллисекундой позже после того, как шаблон `errors` отрисован. Помните, как мы говорили что перенаправление на другую страницу осуществляется мгновенно? Это означает, перенаправление произойдет до того, как будет вызван обработчик `defer`.
Это то что нам нужно - если обработчик не будет вызван, ошибка не будет отмечена как `seen`, и она не будет удалена из коллекции - соответственно, мы увидим ее на странице, куда был перенаправлен пользователь:
~~~js
Template.errors.helpers({
errors: function() {
return Errors.find();
}
});
Template.error.rendered = function() {
var error = this.data;
Meteor.defer(function() {
Errors.update(error._id, {$set: {seen: true}});
});
};
~~~
<%= caption "client/views/includes/errors.js" %>
<%= highlight "7~12" %>
<%= commit "9-3", "Следим за тем какие ошибки уже были показаны. Очищаем ошибки в роутере." %>
Функция `rendered` будет вызвана, как только наш шаблон будет отрисован в браузере. Внутри этой функции `this` указывает на текущий объект шаблона, а `this.data` позволит нам обратиться к параметрам объекта, который мы отрисовываем (в данном случае, ошибке).
Фух! Куча работы ради чего-то, что пользователи возможно никогда не увидят!
<% note do %>
### Функция-обработчик `rendered`
Функция `rendered` у шаблона вызывается каждый раз, когда шаблон отрисован браузером. Это включает в себя первый раз, когда шаблон появляется на экране. он также будет вызван каждый раз, когда шаблон будет отрисован заново - то есть каждый раз, когда любые из его данных изменятся.
Эти обработчики будут вызываться как минимум дважды - когда приложение загрузится, и когда данные из коллекций будут загружены в шаблон. По этой причине стоит быть осторожным с кодом, который не должен быть вызван дважды (например, диалоги с сообщениями или код отслеживания посещений).
<% end %>