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>#</th>
<th>Track</th>
<th>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);
$result = compact('record');
Json::dump($result);
return view('shop.show', $result); // Pass $result to the view
}
1
2
3
4
5
6
7
2
3
4
5
6
7
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 http://localhost:3000/shop/5?dd
- You can't loop and therefore can't use
transform()
- 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-500.jpg";
// Combine artist + title
$record->title = $record->artist . ' - ' . $record->title;
// Links to MusicBrainz API
// https://wiki.musicbrainz.org/Development/JSON_Web_Service
$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 and disabled
$record->btnClass = $record->stock > 0 ? 'btn-outline-success' : 'btn-outline-danger disabled';
// You can't overwrite the attribute genre (object) with a string, so we make a new attribute
$record->genreName = $record->genre->name;
// Use the PHP function number_format() to show 2 decimal digits of the price
$record->price = number_format($record->price,2);
// Hide attributes you don't need for the view
$record->makeHidden(['genre', 'artist', 'genre_id', 'created_at', 'updated_at', 'title_mbid', '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
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
- 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">
<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: € {{ $record->price }}</p>
</div>
<div class="col-sm-8">
<table class="table table-sm">
<thead>
<tr>
<th>#</th>
<th>Track</th>
<th>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
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
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>
// Replace vinyl.png with real cover
$('#cover').attr('src', $('#cover').data('src'));
</script>
@endsection
1
2
3
4
5
6
2
3
4
5
6
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 - We're only interested in the tracks part of the data
- Get tracks info from MusicBrainz API with the Laravel HTTP client
- Get only the JSON data from the result
- Dump-and-die the
$response
from the server
public function show($id)
{
$record = Record::with('genre')->findOrFail($id);
...
$record->makeHidden(['genre', 'artist', 'genre_id', 'created_at', 'updated_at', 'title_mbid', 'genre']);
// get record info and convert it to json
$response = Http::get($record->recordUrl)->json();
dd($response);
$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
2
3
4
5
6
7
8
9
10
11
12
13
14
Narrow the request
- As you can see, we're now getting a whole series of arrays inside arrays, but we're only interested in the tracks array
- Add an extra line that filters only the tracks array out of the JSON response (and stores the result in
$tracks
) - Dump-and-die
$tracks
instead of$response
- Add the tracks to the
compact()
function
- Add an extra line that filters only the tracks array out of the JSON response (and stores the result in
$response = Http::get($record->recordUrl)->json();
$tracks = $response['media'][0]['tracks'];
$result = compact('tracks', 'record');
Json::dump($result);
return view('shop.show', $result); // Pass $result to the view
1
2
3
4
5
6
2
3
4
5
6
Transform the request
- As in the previous chapter, we want to remove all unnecessary fields
$tracks
is an ordinary array, but thetransform()
method only works on a collection!- Convert the
$tracks
array to a$tracks
collection (wrap the array inside thecollect()
method) - Transform the collection and remove the unnecessary keys using the PHP function
unset()
- Convert the
$response = Http::get($record->recordUrl)->json();
$tracks = $response['media'][0]['tracks'];
$tracks = collect($tracks)
->transform(function ($item, $key) {
unset($item['id'], $item['recording'], $item['number']);
return $item;
});
$result = compact('tracks', 'record');
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- Update the table (
tbody
) inside the view
<tbody>
@foreach($tracks as $track)
<tr>
<td>{{ $track['position'] }}</td>
<td>{{ $track['title'] }}</td>
<td>{{ $track['length'] }}</td>
</tr>
@endforeach
</tbody>
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
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 inside the controller
- Transform
$item['length']
with the PHP date function
- Transform
$tracks = collect($tracks)
->transform(function ($item, $key) {
$item['length'] = date('i:s', $item['length'] / 1000); // PHP works with sec, not ms!!!
unset($item['id'], $item['recording'], $item['number']);
return $item;
});
1
2
3
4
5
6
2
3
4
5
6
EXERCISE: iTunes top songs Belgium
- Use the native Laravel HTTP-method to show the top 12 of Belgian iTunes songs for today
- Create a new Controller (ItunesController.php), a route (http://localhost:3000/itunes) and a view for this route
- Get the top 12 Belgian songs from the iTunes API:
- Feed generator: https://rss.applemarketingtools.com/
- JSON response: https://rss.applemarketingtools.com/api/v2/be/music/most-played/12/songs.json
TIP
This is a live feed and the content changes daily. Compare your result with the live preview (@it-fact.be)
- The
h1
-tag contains thetitle
and the land code (country
) in uppercase - Create a Bootstrap card (
artworkUrl100
,artistName
,name
, the firstname
inside the genres array andartistUrl
) for every record - Show the
updated
date beneath all cards
TIP
Use the native PHP date functions or use the built-in Carbon library to reformat the date