-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy path09-errors.md.erb
221 lines (159 loc) · 10.5 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: Błędy
slug: errors
date: 0009/01/01
number: 9
contents: Utworzenie lepszego mechanizmu wyświetlania błędów i komunikatów.|Jak używać `Template.rendered` aby rozpoznać, kiedy użytkownik zobaczył błąd.|Używanie filtra routera aby upewnić się, że błąd został wyświetlony tylko raz.
paragraphs: 31
---
Używanie standardowego mechanizmu przeglądarki, czyli okna dialogowego `alert()` aby ostrzegać użytkownika o problemie z dodaniem danych wydaje się nieco niezadowalające i z pewnością nie tworzy świetnego interfejsu użytkownika. Możemy to udoskonalić.
Zamiast tego rozwiązania, zbudujemy wszechstronny mechanizm raportowania błędów, który będzie wykonywał lepiej zadanie przekazywania błędów bez zatrzymywania pracy użytkownika.
### Wprowadzenie do lokalnych kolekcji
Zaimplementujemy prosty system, który śledzi, które błędy użytkownik zobaczył i wyświetla nowe błędy w odpowienim miejscu "migoczących wiadomości" na stronie. Ten wzorzec UX jest użyteczny, gdy chcemy poinformować użytkownika, że coś się stało bez przerywania ich pracy.
Utworzymy coś podobnego do migoczących powiadomień znanych z aplikakcji Ruby on Rails, ale jest bardziej subtelne, bo zaimplementowane po stronie klienta i rozpoznające kiedy użytkownik zobaczył wiadomość.
Aby rozpocząć, utworzymy kolekcję, w której będziemy zapamiętywać błędy. Pod warunkiem, że błędy są przeznaczone tylko do bieżącej sesji i nie muszą być w żaden sposób zapisywane, zrobimy coś nowego i stworzymy _lokalną kolekcję_. Oznacza to, że kolekcja `Errors` będzie istniała tylko w przeglądarce po stronie klienta i nie będzie starała się synchronizować z serwerem.
Aby tego dokonać, stworzymy komunikat błędu w pliku znajdującym się tylko po stronie klienta, z nazwą kolekcji ustawioną na `null`. Tworzymy funkcję `throwError` która po prostu wstawia błąd w nowo utworzoną lokalną kolekcję:
~~~js
// Local (client-only) collection
Errors = new Meteor.Collection(null);
~~~
<%= caption "client/helpers/errors.js" %>
Teraz gdy kolekcja została utworzona, możemy dodać funkcję `throwError`, którą będziemy wołali aby dodać do niej błędy. Nie musimy się martwić o `allow` czy `deny` czy cokolwiek podobnego, ponieważ jest to kolekcja lokalna, która nie będzie zapisywana w bazie danych Mongo.
~~~js
throwError = function(message) {
Errors.insert({message: message})
}
~~~
<%= caption "client/helpers/errors.js" %>
Zaletą używania lokalnej kolekcji do przechowywania błędów jest to, że jak wszystkie kolekcje, jest reaktywna -- oznacza to, że możemy wyświetlać błędy w ten sam sposób, co jakiekolwiek inne dane z kolekcji.
### Wyświetlanie błędów
Zamierzamy wyświetlać błędy na samej górze strony:
~~~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" %>
Utwórzmy teraz szablony `errors` i `error` w `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 %>
### Bliźniacze szablony
Zauważysz, że wrzucamy dwa szablony do jednego pliku. Do tej pory staraliśmy się stosować zasadę "jeden plik, jeden szablon", ale jeżeli chodzi o Meteora, wrzucenie wszystkich szablonów do jednego pliku działa równie dobrze (chociaż stworzyłoby to bardzo zagmatwany `main.html` !)
W tym przypadku, ponieważ oba szablony błędów są relatywnie krótkie, zrobimy wyjątek i wrzucimy je do tego samego pliku aby uporządkować repozytorium.
<% end %>
Potrzebujemy tylko zintegrować nagłówek szablony i można zaczynać!
~~~js
Template.errors.helpers({
errors: function() {
return Errors.find();
}
});
~~~
<%= caption "client/views/includes/errors.js" %>
<%= commit "9-1", "Podstawowe raportowanie błędów." %>
### Tworzenie błędów
wiemy jak wyświetlać błędy, ale potrzebujemy utworzyć kilka zanim będzie można cokolwiek zobaczyć. Błędy są najczęściej uaktywniane przez użytkowników wproadzających nowe dane, zatem będziemy sprawdzali błędy w callbacku tworzenia postów i wyświetlać wiadomość dla jakichkolwiek błędów, które powstają.
Dodatkowo, jeżeli otrzymamy błąd `302` (który oznacza, że post o tym samym URL już istnieje), przekierujemy użytkownika to bieżącego posta. Otrzymamy `_id` bieżącego posta z `error.details` (pamiętaj, że przekazaliśmy `_id` posta jako trzeci parametr do `details` naszej klasy `Errors` w rozdziale 7).
~~~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) {
// display the error to the user
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", "Używanie raportowania błędów." %>
Sprawdź to: spróbuj stworzyć posta i przejść do URL `http://meteor.com`. Ponieważ ten URL jest już dołączony do posta w danych początkowych (fixtures), powinieneś zobaczyć:
<%= screenshot "9-1", "Wyzwalanie błędu" %>
### Usuwanie błędów
Możliwe, że kliknąłeś przycisk 'zamknij' po zobaczeniu błędu. Jeżeli to zrobiłeś, błąd powinien zniknąć i pojawić się zaraz po tym, jak wczytasz kolejną stronę do przeglądarki. O co chodzi?
Przycisk 'zamknij' wyzwala kod JavaScript w Boostrap Twittera: nie ma nic wspólnego z Meteorem! Co się dzieje: Bootstrap usuwa element `<div>` z DOM zawierający błąd, ale nie z kolekcji Meteora. Oznacza to, że błąd pojawi się ponownie po ponownym przerenderowaniu strony.
Z tego względu jeżeli nie chcesz powstrzymać nieustającego pojawiania się błędu za każdym razem i wprawić użytkownika w obłęd, zaimplementujmy rozwiązanie umożliwiające usuwanie błędów także z kolekcji.
Na początek zmienimy funkcję `throwError` aby wziąć pod uwagę właściwość `seen` (ang. widziany). Będzie to przydatne później do śledzenia faktu, czy błąd został już widziany przez użytkownika.
Gdy jest to gotowe, możemy zaimplementować funkcję `clearErrors`, która czyści te `widziane` błędy:
~~~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" %>
Następnie, wyczyścimy błędy w routerze aby nawigacja do następnej strony spowoduje usunięcie tych błędów na zawsze:
~~~js
// ...
Router.before(requireLogin, {only: 'postSubmit'})
Router.before(function() { clearErrors() });
~~~
<%= caption "lib/router.js" %>
<%= highlight "4" %>
Aby funkcja `clearErrors()` poprawnie wykonała swoją pracę, błędy muszą być zaznaczane jako `widziane`. Aby przebiegało to prawidłowo, musimy zatroszczyć się o obsługę jednego przypadku wyjątkowego: gdy wyrzucamy błąd i przekierowujemy użytkownika do innego miejsca (tak jak przy próbie utworzenia posta ze zduplikowanym linkiem), przekierowanie jest natychmiastowe. Oznacza to, że użytkownik nigdy nie dostanie szansy na zobaczenie błędu zanim zostanie on usunięty.
Jest to miejsce, w którym przyda się zmienna `seen` (widziany). Musimy się upewnić, że jest ustawiana na `true` wyłącznie wtedy, gdy użytkownik zobaczył błąd.
Aby tego dokonać, użyjemy funkcji `Meteor.defer()`. Funkcja ta przekazuje Meteorowi, aby uruchomić callback "zaraz po tym" co dzieje się w tej chwili. Jeżeli pomoże to zrozumieć, możesz pomyśleć, że `defer()` to jak przekazanie przeglądarce aby poczekać 1 milisekundę przed kontynuowaniem pracy.
Osiągneliśmy to, że Meteor ustawia `seen` na `true` 1 milisekundę po renderowaniu szablonu `template`. Ale czy pamiętasz jak wspomnieliśmy, że przekierowanie dzieje się natychmiastowo? Oznacza to, że mechanizm przekierowania zostanie uruchomiony przed callbackiem `defer`, który nigdy nie będzie miał możliwości na wykonanie.
Jest to dokładnie to, co chcieliśmy osiągnąć: jeżeli nie został wykonany, nasz błąd nie zostanie zaznaczony jako `widziany`, co oznacza, że nie będzie usunięty, co również oznacza, że pojawi się na stronie na którą użytkownik został przekierowany, dokładnie tak, jak chcieliśmy!
~~~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", "Monitorowanie które błędy były wyświetlone i czyszczenie ich w routerze." %>
Callback `rendered` jest wywołany ponownie po renderowaniu szablonu w przeglądarce. W środku callback'a `this` odnosi się do bieżącej instancji szablonu, a `this.data` pozwala na dostęp do danych aktualnie renderowanego obiektu (w naszym przypadku, komunikatu błędu).
Świetnie! Dużo pracy było wykonane, aby zaimplementować coś, czego użytkownicy miejmy nadzieję nigdy nie zobaczą!
<% note do %>
### Callback `rendered`
Callback `rendered` szablonu jest wywoływany za każdym razem po renderowaniu szablonu w przeglądarce. Zawiera to oczywiście pierwsze renderowanie, gdy pojawia się na ekranie, ale warto zapamiętać, że callback będzie także wywoływany za każdym razem, gdy szablon jest renderowany ponownie tj. za każdym razem, gdy jego dane ulegną zmianie.
Callback `rendered` jest ogolnie wywoływany przynajmniej dwukrotnie: za pierwszym razem, gdy aplikakcja zostaje załadowana i za drugim razem gdy dane kolekcji zostają wczytane. Powinieneś być zatem ostrożny przy wstawianiu kodu, który nie powinien zostać uruchomiony dwukrotnie (np. alert, czy kod śledzący zdarzenia na stronie).
<% end %>