Shop: detail page
- Open the detail view resources/views/shop/show.blade.php
- Open the controller app/Http/Controllers/ShopController.php
Basic view
- Create a basic, static skeleton for the record details
@section('main')
<h1>Artist - Record</h1>
<div class="row">
<div class="col-sm-4 text-center">
<img class="img-thumbnail" id="cover" src="/assets/vinyl.png" data-src="" alt="">
<p>
<a href="#!" class="btn btn-sm btn-block mt-3">
<i class="fas fa-cart-plus mr-3"></i>Add to cart
</a>
</p>
<p class="text-left">Genre: <br>
Stock: <br>
Price: € </p>
</div>
<div class="col-sm-8">
<table class="table table-sm">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Track</th>
<th scope="col">Length</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
@endsection
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
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
Update ShopController
- You only retrieve one record from the database
- Use the number at the end of the URL to identify the record to be shown
- E.g. http://localhost:3000/shop/5 should give all the details, the genre included, for the record 'Fleetwood Mac - Rumours' with
id
= 5
- E.g. http://localhost:3000/shop/5 should give all the details, the genre included, for the record 'Fleetwood Mac - Rumours' with
Get one record
- Select one record based on the
$id
at the end of the URL using the methodfindOrFail()
public function show($id)
{
$record = Record::with('genre')->findOrFail($id);
//dd($record);
$result = compact('record');
Json::dump($result);
return view('shop.show', $result); // Pass $result to the view
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
REMARK
If the database finds no result for a given id
(e.g. http://localhost:3000/shop/555?json), the method findOrFail()
automatically results in a 404 | Not Found error (in contrast to the method find()
which would result in a null
object)
Transform the record
- The reformatting of
$record
is (a bit) different than the transform of$genres
in the previous section! $genres
from the previous section was a collection with an array ofGenre
objects- See the output below of
dd($genres)
(before the transform) - You can loop over each genre with
transform()
- See the output below of
$record
is NOT a collection, but one object of the classRecord
- See the output below of
dd($record)
- You can't loop and therefore can't use
transform()
- See the output below of
- Transform the properties of the record and remove all properties you don't use inside the view
$record = Record::with('genre')->findOrFail($id);
// dd($record);
// Real path to cover image
$record->cover = $record->cover ?? "https://coverartarchive.org/release/$record->title_mbid/front-250.jpg";
// Combine artist + title
$record->title = $record->artist . ' - ' . $record->title;
// Links to MusicBrainz API (used by jQuery)
// https://wiki.musicbrainz.org/Development/JSON_Web_Service
$record->artistUrl = 'https://musicbrainz.org/ws/2/artist/' . $record->artist_mbid . '?inc=url-rels&fmt=json';
$record->recordUrl = 'https://musicbrainz.org/ws/2/release/' . $record->title_mbid . '?inc=recordings+url-rels&fmt=json';
// If stock > 0: button is green, otherwise the button is red
$record->btnClass = $record->stock > 0 ? 'btn-outline-success' : 'btn-outline-danger';
// You can't overwrite the attribute genre (object) with a string, so we make a new attribute
$record->genreName = $record->genre->name;
// Remove attributes you don't need for the view
unset($record->genre_id, $record->artist, $record->created_at, $record->updated_at, $record->artist_mbid, $record->title_mbid, $record->genre);
$result = compact('record');
Json::dump($result);
return view('shop.show', $result); // Pass $result to the view
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Add data to the view
- Update the view with the data from the query
- Add
title
to the 'title' section (without curly brackets!), to theh1
tag and inside thealt
attribute of the image - Add
cover
inside the data attributedata-src
of the image - Add
btnClass
as an extra class to the button - Add
genreName
,stock
andprice
to the page - If
stock
is 0, add the attributedisabled
to the button
- Add
@section('title', $record->title)
@section('main')
<h1>{{ $record->title }}</h1>
<div class="row">
<div class="col-sm-4 text-center">
<img class="img-thumbnail" id="cover" src="/assets/vinyl.png" data-src="{{ $record->cover }}"
alt="{{ $record->title }}">
<p>
<a href="#!" class="btn {{ $record->btnClass }} btn-sm btn-block mt-3
{{ $record->stock == 0 ? 'disabled' : '' }}">
<i class="fas fa-cart-plus mr-3"></i>Add to cart
</a>
</p>
<p class="text-left">Genre: {{ $record->genreName }}<br>
Stock: {{ $record->stock }}<br>
Price: € {{ number_format($record->price,2) }}</p>
</div>
<div class="col-sm-8">
<table class="table table-sm">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Track</th>
<th scope="col">Length</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
@endsection
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
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
Add dynamic content with jQuery
- Replace the dummy cover with the real one (exactly the same as in previous section) and get all the tracks on the album from the MusicBrainz api
Show the real cover
- Fill the script_after gap in the detail page resources/views/shop/show.blade.php
@section('script_after')
<script>
$(function () {
// Replace vinyl.png with real cover
$('#cover').attr('src', $('#cover').data('src'));
// Get tracks from MusicBrainz API
});
</script>
@endsection
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Get all tracks
- Show the JSON representation of an album, e.g: http://localhost:3000/shop/5?json
- Click on the
recordUrl
value and inspect the JSON response- You find the information about a track inside
media
(an array of one item) andtracks
(an array of x JSON objects) - To get all tracks, loop over
media[0].tracks
- To get the title of the first track:
media[0].tracks[0].title
- You find the information about a track inside
- Get all tracks and add them inside the
tbody
tag of the table- Use the
getJSON()
method that loads JSON-encoded data from the server using a GET HTTP request - Open the browser console to see all the data (or errors)
- Use the
// Get tracks from MusicBrainz API
$.getJSON('{{ $record->recordUrl }}')
.done(function (data) {
console.log(data);
// loop over each track
$.each(data.media[0].tracks, function (key, value) {
// Construct a table row
let row = `<tr>
<td>${value.position}</td>
<td>${value.title}</td>
<td>${value.length}</td>
</tr>`;
// Append the row to the tbody tag
$('tbody').append(row);
});
})
.fail(function (error) {
console.log("error", error);
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Update the track length
- The track length is in ms but we want to format it as mm:ss
- Because we have to format all track lengths this way, it's best practice to do this with a function
- Make a function
to_mm_ss()
and pass the track length to this function - At the moment, you don't see any difference in the view
- Make a function
$(function () {
...
let row = `<tr>
<td>${value.position}</td>
<td>${value.title}</td>
<td>${to_mm_ss(value.length)}</td>
</tr>`;
...
});
function to_mm_ss(duration) {
return duration;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
- Convert the
duration
inside the function to a mm:ss format
function to_mm_ss(duration) {
let seconds = parseInt((duration / 1000) % 60);
let minutes = parseInt(duration / (1000 * 60));
minutes = (minutes < 10) ? '0' + minutes : minutes;
seconds = (seconds < 10) ? '0' + seconds : seconds;
duration = minutes + ':' + seconds;
return duration;
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Move script to global vinylShop.js
- If you need this script on other pages within your site, you should move it to the common script vinylshop.js in our site
- Open resources/js/vinylShop.js and move the function
to_mm_ss(duration)
to this file (don't forget to delete this function from show.blade.php) - Add
export
in front of the function
- Open resources/js/vinylShop.js and move the function
export function to_mm_ss(duration) {
let seconds = parseInt((duration / 1000) % 60);
let minutes = parseInt((duration / (1000 * 60)) % 60);
minutes = (minutes < 10) ? '0' + minutes : minutes;
seconds = (seconds < 10) ? '0' + seconds : seconds;
duration = minutes + ':' + seconds;
return duration;
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- Update the reference inside the Blade template from
to_mm_ss(value.length)
tovinylShop.to_mm_ss(value.length)
let row = `<tr>
<td>${value.position}</td>
<td>${value.title}</td>
<td>${vinylShop.to_mm_ss(value.length)}</td>
</tr>`;
1
2
3
4
5
2
3
4
5
- If the script is working, you don't see any difference on the page
EXERCISE: Track lengths
- Check the detail page for the record 'Cock Sparrer - Forever' (with
id
= 31)
- Notice that the track lengths are 00:00
- Dig into the MusicBrainz record/release details (that we retrieve in JSON-format), and resolve this issue
- Check whether your solution also works for the other records!
Commit "Opdracht 5: <= Shop: detail page"
- Execute the following commands in a terminal window:
git add .
git commit -m "Opdracht 5: <= Shop: detail page"
git push
1
2
3
2
3