-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy path09-errors.md.erb
209 lines (153 loc) · 9.21 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
---
title: Errori
slug: errors
date: 0009/01/01
number: 9
contents: Creare un sistema di visualizzazione di errori e messaggi.|Come usare `Template.rendered` per sapere quando un utente ha visualizzato un errore.|Usare un filtro nel router per mostrare gli errori una sola volta.
paragraphs: 31
---
Usare una finestra di dialogo `alert()` per avvisare un utente che c'è stato un problema con l'inserimento di un post è abbastanza insoddisfacente e di sicuro non contribuisce ad un'ottimale user experience. Si può fare certamente di meglio.
Si può costruire un meccanismo più versatile per riportare gli errori che di sicuro è una scelta migliore che farlo bloccando il flusso di utilizzo del browser.
### Collezioni Locali
Verrà implementato un semplice sistema che tiene traccia di quali errori un utente ha visto e mostra quelli nuovi in un'area "flash" del sito. Questo pattern è utile per informare l'utente che è successo qualcosa senza però infastidire la sua esperienza nell'uso dell'applicazione.
Quello che verrà creato è un sistema simile ai messaggi flash che si trovano spesso nelle applicazioni Ruby on Rails, ma è più ingegnoso per il fatto che è implementato lato client e riconosce quando l'utente visualizza il messaggio.
Per iniziare, si crea una collezione per contenere gli errori. Dato che gli errori sono rilevanti solo nella sessione corrente e non devono in alcun modo essere persistenti, si crea qualcosa di nuovo, una _collezione locale_. Ciò significa che la collezione `Errors` esisterà solo nel browser, e non tenterà di sincronizzarsi sul server.
Per fare ciò, si crea l'errore in un file disponibile solo al client, con in nome della collezione impostato a `null`. Grazie a una funzione `throwError`, con la quale gli errori vengono inseriti nella nuova collezione locale:
~~~js
// Local (client-only) collection
Errors = new Meteor.Collection(null);
~~~
<%= caption "client/helpers/errors.js" %>
Ora che la collezione è creata, si aggiunge una funzione `throwError` per inserirvi gli errori. Non bisogna preoccuparsi di `allow`, `deny` o altro, perché si tratta di una collezione locale e non verrà salvata sul database di Mongo.
~~~js
throwError = function(message) {
Errors.insert({message: message})
}
~~~
<%= caption "client/helpers/errors.js" %>
Il vantaggio di usare una collezione locale per contenere gli errori è che, come tutte le collezioni, è un elemento reattivo -- il che significa che si possono mostrare gli errori nello stesso modo in cui si mostrano i dati delle altre collezioni.
### Mostrare gli errori
Gli errori verranno mostrati al di sopra del layout principale:
~~~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 "4" %>
Si creino ora i template `errors` e `error` nel file `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 %>
### Template gemelli
Si noti che sono stati inseriti due template in un solo file. Fino ad ora si è cercato di mantenere la convenzione "un file, un template", ma per come è fatto Meteor è possibile mettere tutti i template in un file solo e non ci sarebbero problemi di funzionamento (anche se si avrebbe un file `main.html` molto confuso!).
In questo caso, dato che entrambi i template degli errori sono molto piccoli, si fa un'eccezione e si inseriscono nello stesso file per avere un repository un po' più pulito.
<% end %>
Ora si deve solo creare l'helper dei template e si è pronti per proseguire!
~~~js
Template.errors.helpers({
errors: function() {
return Errors.find();
}
});
~~~
<%= caption "client/views/includes/errors.js" %>
<%= commit "9-1", "Basic error reporting." %>
### Creare gli errori
Ora che si sa come mostrare gli errori, si deve crearne qualcuno perché si possa vedere qualcosa. Gli errori spesso si generano quando un utente inserisce nuovi contenuti, per cui il controllo viene inserito nella callback della creazione di un post per mostrare un messaggio per ogni errori che viene generato.
In aggiunta, se ritorna un errore `302` (che significa che esiste già un post con lo stesso URL), l'utente verrà reindirizzato al post esistente. Si ottiene l'`_id` del post esistente da `error.details` (si ricordi che l'`_id` del post è passato come terzo argomento `details` nella classe `Error` nel capitolo 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", "Actually use the error reporting." %>
Facciamo una prova: creiamo un post inserendo come URL `http://meteor.com`. Dato che questo URL è già collegato ad un altro post, dovrebbe vedersi un errore:
<%= screenshot "9-1", "Triggering an error" %>
### Rimuovere gli errori
Se avete provato a cliccare sul bottone di chiusura dell'errore, avrete visto l'errore sparire perché il bottone di chiusura scatena l'evento JavaScript di Twitter Bootstrap.
Quello che succede è che Bootstrap rimuove l'elemento `<div>` dell'errore dal DOM, ma non rimuove l'oggetto dell'errore dalla collezione di Meteor. Facciamo quindi in modo di ripulire la nostra collezione locale dopo che il messaggio di errore ha fatto il suo lavoro.
Come prima cosa, modificheremo la funzione `throwError` per includere una proprietà `seen` (visto). Questo ci aiuterà in seguito a tenere traccia del fatto che un errore sia stato visto o meno da un utente.
Una volta fatto, si può scrivere una semplice funzione `clearErrors` che rimuova gli errori "visualizzati":
~~~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" %>
Ripuliamo ora gli errori nel router in modo che navigando su un'altra pagina questi errori spariscano per sempre:
~~~js
// ...
Router.onBeforeAction(requireLogin, {only: 'postSubmit'})
Router.onBeforeAction(function() { clearErrors() });
~~~
<%= caption "lib/router.js" %>
<%= highlight "4" %>
Per fare in modo che la funzione `clearErrors()` faccia il proprio lavoro, gli errori devono essere marcati come `seen`. Per farlo in maniera appropriata dobbiamo tenere conto di un caso limite: quando viene alzato un errore e l'utente viene poi reindirizzato ad un'altra pagina (ad esempio quando si tenta di inserire un link duplicato), il reindirizzamento avviene in maniera istantanea. Ciò significa che l'utente non ha mai avuto modo di vedere l'errore prima che venga cancellato.
Qui è dove la proprietà `seen` viene in aiuto. Dobbiamo assicurarci che sia impostata a `true` solo se l'utente ha realmente visto l'errore.
Per farlo useremo `Meteor.defer()`. Questa funzione dice a Meteor di eseguire la propria callback "appena dopo" qualsiasi cosa stia accadendo al momento. Se può aiutare, si può considerare che usare `defer()` è come dire al browser di aspettare un millisecondo prima di procedere.
Si sta dicendo a Meteor di impostare `seen` a `true` un millisecondo dopo che il template `errors` e stato renderizzato. Ricordate che abbiamo detto che il reindirizzamento accade in maniera istantanea? Questo significa che il reindirizzamento avverrà prima della callback `defer`, che quindi non sarà mai eseguita.
È esattamente ciò che si stava cercando: se non viene eseguita l'errore non sarà mai marcato come `seen`, il che significa che non sarà eliminato, e quindi apparirà sulla pagina alla quale l'utente è stato reindirizzato esattamente come desiderato!
~~~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", "Monitor which errors have been seen, and clear on routing." %>
La callback `rendered` viene chiamata quando i template è renderizzato nel browser. Dentro la callback, `this` si riferisce all'istanza corrente del template, e `this.data` permette di accedere ai dati dell'oggetto che è al momento renderizzato (in questo caso, un errore).
Wow! Abbiamo fatto un sacco di lavoro per qualcosa che si spera gli utenti non vedano mai!