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
|
---
title: ジャンル詳細ページ
slug: Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page
translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page
---
<p>The genre <em>detail</em> page needs to display the information for the particular genre instance using its automatically generated <code>_id</code> field value as the identifier. The page should display the genre name, and a list of all books in the genre with links to each book's details page.</p>
<h2 id="Controller">Controller</h2>
<p>Open <strong>/controllers/genreController.js</strong> and import the <em>async</em> and <em>Book</em> modules at the top of the file.</p>
<pre class="brush: js">var Book = require('../models/book');
var async = require('async');
</pre>
<p>Find the exported <code>genre_detail</code><code>()</code> controller method and replace it with the following code.</p>
<pre class="brush: js">// Display detail page for a specific Genre.
exports.genre_detail = function(req, res, next) {
<strong> async.parallel({
genre: function(callback) {
Genre.findById(req.params.id)
.exec(callback);
},
genre_books: function(callback) {
Book.find({ 'genre': req.params.id })
.exec(callback);
},
}, function(err, results) {
if (err) { return next(err); }
if (results.genre==null) { // No results.
var err = new Error('Genre not found');
err.status = 404;
return next(err);
}
// Successful, so render
res.render('genre_detail', { title: 'Genre Detail', genre: results.genre, genre_books: results.genre_books } );
});</strong>
};
</pre>
<p>The method uses <code>async.parallel()</code> to query the genre name and its associated books in parallel, with the callback rendering the page when (if) both requests complete successfully.</p>
<p>The ID of the required genre record is encoded at the end of the URL and extracted automatically based on the route definition (<strong>/genre/:id</strong>). The ID is accessed within the controller via the request parameters: <code style="font-style: normal; font-weight: normal;">req.params.id</code>. It is used in <code style="font-style: normal; font-weight: normal;">Genre.findById()</code> to get the current genre. It is also used to get all <code>Book</code> objects that have the genre ID in their <code>genre</code> field: <code>Book.find({ 'genre': req.params.id })</code>.</p>
<div class="note">
<p><strong>Note:</strong> If the genre does not exist in the database (i.e. it may have been deleted) then <code>findById()</code> will return successfully with no results. In this case we want to display a "not found" page, so we create an <code>Error</code> object and pass it to the <code>next</code> middleware function in the chain. </p>
<pre class="brush: js"><strong>if (results.genre==null) { // No results.
var err = new Error('Genre not found');
err.status = 404;
return next(err);
}</strong>
</pre>
<p>The message will then propagate through to our error handling code (this was set up when we <a href="/ja/docs/Learn/Server-side/Express_Nodejs/skeleton_website#error_handling">generated the app skeleton</a> - for more information see <a href="/ja/docs/Learn/Server-side/Express_Nodejs/Introduction#Handling_errors">Handling Errors</a>).</p>
</div>
<p>The rendered view is <strong>genre_detail</strong> and it is passed variables for the <code>title</code>, <code>genre</code> and the list of books in this genre (<code>genre_books</code>).</p>
<h2 id="View">View</h2>
<p>Create <strong>/views/genre_detail.pug</strong> and fill it with the text below:</p>
<pre class="brush: js">extends layout
block content
<strong>h1 Genre: #{genre.name}</strong>
div(style='margin-left:20px;margin-top:20px')
h4 Books
dl
each book in genre_books
dt
a(href=book.url) #{book.title}
dd #{book.summary}
else
p This genre has no books
</pre>
<p>The view is very similar to all our other templates. The main difference is that we don't use the <code>title</code> passed in for the first heading (though it is used in the underlying <strong>layout.pug</strong> template to set the page title).</p>
<h2 id="What_does_it_look_like">What does it look like?</h2>
<p>Run the application and open your browser to <a href="http://localhost:3000/">http://localhost:3000/</a>. Select the <em>All genres</em> link, then select one of the genres (e.g. "Fantasy"). If everything is set up correctly, your page should look something like the following screenshot.</p>
<p><img alt="Genre Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14462/LocalLibary_Express_Genre_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 523px; margin: 0px auto; width: 1000px;"></p>
<div class="note">
<p>You might get an error similar to this:</p>
<pre class="brush: bash">Cast to ObjectId failed for value " 59347139895ea23f9430ecbb" at path "_id" for model "Genre"
</pre>
<p>This is a mongoose error coming from the <strong>req.params.id</strong>. To solve this problem, first you need to require mongoose on the <strong>genreController.js</strong> page like this:</p>
<pre class="brush: js"> var mongoose = require('mongoose');
</pre>
<p>Then use <strong>mongoose.Types.ObjectId() </strong>to convert the id to a that can be used. For example:</p>
<pre class="brush: js">exports.genre_detail = function(req, res, next) {
var id = mongoose.Types.ObjectId(req.params.id);
...
</pre>
</div>
<h2 id="Next_steps">Next steps</h2>
<ul>
<li>Return to <a href="/ja/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li>
<li>Proceed to the next subarticle of part 5: <a href="/ja/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page">Book detail page</a>.</li>
</ul>
|