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

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

Get one record

  • Select one record based on the $id at the end of the URL using the method findOrFail()


 
 
 
 


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

One record

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 of Genre objects
    • See the output below of dd($genres) (before the transform)
    • You can loop over each genre with transform()

Collection of genres

  • $record is NOT a collection, but one object of the class Record

One record object

  • 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

Transform $record

Add data to the view

  • Update the view with the data from the query
    • Add title to the 'title' section (without curly brackets!), to the h1 tag and inside the alt attribute of the image
    • Add cover inside the data attribute data-src of the image
    • Add btnClass as an extra class to the button
    • Add genreName, stock and price to the page
 


 


 


 



 
 
 

















@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

Add data to the view

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

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

Find tracks on MusicBrains API

  • 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

Get all record info

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

 

 



$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

Filter the tracks info

Transform the request

  • As in the previous chapter, we want to remove all unnecessary fields
  • $tracks is an ordinary array, but the transform() method only works on a collection!
    • Convert the $tracks array to a $tracks collection (wrap the array inside the collect() method)
    • Transform the collection and remove the unnecessary keys using the PHP function unset()


 
 
 
 
 



$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

Transform the tracks info

  • 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

Add tracks to table

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


 




$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

Convert track length to mm:ss

EXERCISE: iTunes top songs Belgium

TIP

This is a live feed and the content changes daily. Compare your result with the live preview (@it-fact.be)

iTunes JSON response

  • The h1-tag contains the title and the land code (country) in uppercase
  • Create a Bootstrap card (artworkUrl100, artistName, name, the first name inside the genres array and artistUrl) 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

iTunes to view

Last Updated: 11/21/2021, 11:43:55 AM