Appearance
Shop: detail section
- Clicking on "show tracks" will show a popup with the tracks of the album
- The tracks are not stored in the database, but are retrieved from the MusicBrainz API where the attribute
mb_id
in our database is the unique key of the album- E.g. the album Rumours by Fleetwood Mac has the mb_id value 081ea37e-db59-4332-8cd2-ad020cb93af6
- https://musicbrainz.org/ws/2/release/081ea37e-db59-4332-8cd2-ad020cb93af6?inc=recordings&fmt=json
- We will extract the tracks (position, title and length) from the JSON response and show them in a table
Get selected record
- We will use the
wire:click
attribute on the Show Tracks icon to pass theid
of the selected record to theshowTracks
method in the component
Pass the id of the selected record
- The
showTracks
method will retrieve the record from the database and store it in a new public property$selectedRecord
- Add the
wire:click
attribute to the Show Tracks icon
php
<button class="w-6 hover:text-red-900">
<x-phosphor-music-notes-light
wire:click="showTracks({{ $record->id }})"
class="outline-0"
data-tippy-content="Show tracks"/>
</button>
<button class="w-6 hover:text-red-900">
<x-phosphor-music-notes-light
wire:click="showTracks({{ $record->id }})"
class="outline-0"
data-tippy-content="Show tracks"/>
</button>
1
2
3
4
5
6
2
3
4
5
6
Route Model Binding
- Instead of only the
id
we want to store the complete record in the public property$selectedRecord
- The long way is to query the database for the record with the
id
and store the result in the public property$selectedRecord
- But there is a better way: we can use Laravel's Route Model Binding to get the complete record with only the
id
as a parameter in the methodshowTracks
- Line 1: type-hint the parameter
$record
with the modelRecord
=> replaceshowTracks($record)
withshowTracks(Record $record)
- Line 4: dump the property
$selectedRecord
as an array to the page (add->toArray()
)
php
public function showTracks(Record $record)
{
$this->selectedRecord = $record;
dump($this->selectedRecord->toArray());
}
public function showTracks(Record $record)
{
$this->selectedRecord = $record;
dump($this->selectedRecord->toArray());
}
1
2
3
4
5
2
3
4
5
Show the tracks in a modal
- Now we have the complete record in the public property
$selectedRecord
, we can use it to get the tracks and show them in a modal - We will use the Jetstream dialog modal for this
- Let's start with a simple, static modal
php
{{-- Detail section --}}
<x-dialog-modal>
<x-slot name="title">Title</x-slot>
<x-slot name="content">Content</x-slot>
<x-slot name="footer">Footer</x-slot>
</x-dialog-modal>
{{-- Detail section --}}
<x-dialog-modal>
<x-slot name="title">Title</x-slot>
<x-slot name="content">Content</x-slot>
<x-slot name="footer">Footer</x-slot>
</x-dialog-modal>
1
2
3
4
5
6
2
3
4
5
6
Jetstream dialog modal explained
REMARKS
- It's important to get some basic knowledge about the Jetstream dialog modal before you continue
- You're probably going to use this modal in other projects as well, so it's good to know how it works
- Check this YouTube video for or more in-depth information about the Jetstream dialog modal:
- The Jetstream dialog modal is a component that is used to show/hide a modal with a title, content and footer
- More specifically, it's a component that's build on top of a second component
- Ctrl + click on the
x-dialog-modal
tag to see the code of the first component- This component contains tree slots (
$title
,$content
and$footer
) that are wrapped in inside ax-modal
component
- This component contains tree slots (
- Ctrl + click on the
x-modal
tag to see the code of the second component- The most important part of this component is
show: $entangle($attributes->wire('model'))
and Alpinesx-show="show"
directive - This is where the connection between the modal and our
$showModal
property will be made
- The most important part of this component is
- Ctrl + click on the
- When clicking outside the modal, the modal will close
Show and hide the modal
- Line 2: add the
wire:model
attribute to thex-dialog-modal
tag and set it to the public property$showModal
- This is the connection between the modal and our
$showModal
property in the component
- This is the connection between the modal and our
- Line 5: make the footer empty
php
{{-- Detail section --}}
<x-dialog-modal wire:model="showModal">
<x-slot name="title">Title</x-slot>
<x-slot name="content">Content</x-slot>
<x-slot name="footer"></x-slot>
</x-dialog-modal>
{{-- Detail section --}}
<x-dialog-modal wire:model="showModal">
<x-slot name="title">Title</x-slot>
<x-slot name="content">Content</x-slot>
<x-slot name="footer"></x-slot>
</x-dialog-modal>
1
2
3
4
5
6
2
3
4
5
6
Title slot
- Let's add the artist, the record title and the cover image to the title slot
- Paste the following code just before the last closing
<x-slot name="title">
- When the page first loads, the property
$selectedRecord
is empty and to avoid errors, we need to catch this in the model- Line 6: if
$selectedRecord->cover
doesn't exist, show a dummy cover image - Line 8: if
$selectedRecord->title
doesn't exist, use an empty string - Line 7: if
$selectedRecord->artist
doesn't exist, use an empty string - Line 12: if
$selectedRecord
has notracks
, hide the table- Later, we push the
tracks
in the array, so for now it doesn't exist yet
- Later, we push the
- Line 6: if
php
{{-- Detail section --}}
<x-dialog-modal wire:model="showModal">
<x-slot name="title">
<div class="flex items-center border-b border-gray-300 pb-2 gap-4">
<img class="w-20 h-20"
src="{{ $selectedRecord->cover ?? asset('storage/covers/no-cover.png') }}" alt="">
<h3 class="font-medium">
{{ $selectedRecord->title ?? '' }}<br/>
<span class="font-light">{{ $selectedRecord->artist ?? '' }}</span>
</h3>
</div>
</x-slot>
<x-slot name="content">Content</x-slot>
<x-slot name="footer"></x-slot>
</x-dialog-modal>
{{-- Detail section --}}
<x-dialog-modal wire:model="showModal">
<x-slot name="title">
<div class="flex items-center border-b border-gray-300 pb-2 gap-4">
<img class="w-20 h-20"
src="{{ $selectedRecord->cover ?? asset('storage/covers/no-cover.png') }}" alt="">
<h3 class="font-medium">
{{ $selectedRecord->title ?? '' }}<br/>
<span class="font-light">{{ $selectedRecord->artist ?? '' }}</span>
</h3>
</div>
</x-slot>
<x-slot name="content">Content</x-slot>
<x-slot name="footer"></x-slot>
</x-dialog-modal>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Get the tracks from the API
- Get tracks info from MusicBrainz API with the Laravel HTTP client
- Get only the JSON data from the result
- Line 4: the URL which contains the
mb_id
of the selected record - Line 5:
$response
fetches the URL with the Laravel HTTP client (includeuse Http;
) and convert the result to JSON data
($response
contains all the data from the API, like we can see in the first screenshot of this chapter) - Line 6:
- push a new key
tracks
to the selected record$this->selectedRecord['tracks']
- select only the tracks-array in the response (
$response['media'][0]['tracks']
) and add them to thetracks
key
- push a new key
php
public function showTracks(Record $record)
{
$this->selectedRecord = $record;
$url = "https://musicbrainz.org/ws/2/release/{$record->mb_id}?inc=recordings&fmt=json";
$response = Http::get($url)->json();
$this->selectedRecord['tracks'] = $response['media'][0]['tracks'];
$this->showModal = true;
}
public function showTracks(Record $record)
{
$this->selectedRecord = $record;
$url = "https://musicbrainz.org/ws/2/release/{$record->mb_id}?inc=recordings&fmt=json";
$response = Http::get($url)->json();
$this->selectedRecord['tracks'] = $response['media'][0]['tracks'];
$this->showModal = true;
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Show tracks in the content slot
- Line 7 - 26: the
@isset()
directive checks if the property$selectedRecord->tracks
exists- If it exists, show the table with the tracks
- If it doesn't exist, (when the page first loads) show nothing
- Line 17 - 23: loop over the tracks and show the title and duration (you don't need a
wire:key
here, because it's a static list) - Line 19 - 21: show the
position
, thetitle
and the duration (length
) of each track
php
{{-- Detail section --}}
<x-dialog-modal wire:model="showModal">
<x-slot name="title">
...
</x-slot>
<x-slot name="content">
@isset($selectedRecord->tracks)
<table class="w-full text-left align-top">
<thead>
<tr>
<th class="px-4 py-2">#</th>
<th class="px-4 py-2">Track</th>
<th class="px-4 py-2">Length</th>
</tr>
</thead>
<tbody>
@foreach($selectedRecord['tracks'] as $track)
<tr class="border-t border-gray-100">
<td class="px-4 py-2">{{ $track['position'] }}</td>
<td class="px-4 py-2">{{ $track['title'] }}</td>
<td class="px-4 py-2">{{ $track['length'] }}</td>
</tr>
@endforeach
</tbody>
</table>
@endisset
</x-slot>
<x-slot name="footer"></x-slot>
</x-dialog-modal>
{{-- Detail section --}}
<x-dialog-modal wire:model="showModal">
<x-slot name="title">
...
</x-slot>
<x-slot name="content">
@isset($selectedRecord->tracks)
<table class="w-full text-left align-top">
<thead>
<tr>
<th class="px-4 py-2">#</th>
<th class="px-4 py-2">Track</th>
<th class="px-4 py-2">Length</th>
</tr>
</thead>
<tbody>
@foreach($selectedRecord['tracks'] as $track)
<tr class="border-t border-gray-100">
<td class="px-4 py-2">{{ $track['position'] }}</td>
<td class="px-4 py-2">{{ $track['title'] }}</td>
<td class="px-4 py-2">{{ $track['length'] }}</td>
</tr>
@endforeach
</tbody>
</table>
@endisset
</x-slot>
<x-slot name="footer"></x-slot>
</x-dialog-modal>
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
Reformat the track length with Carbon
- The track length is in milliseconds, so we need to convert it to mm:ss
- You can make a custom function for it that handles the transformation, but we prefer to use the Carbon package for this
- Carbon is an easy to use PHP extension for date/time manipulation and formatting
- Carbon is by default installed in in every Laravel project
- The Carbon method we need is
createFromTimestampMs($milliseconds)->format('i:s')
createFromTimestampMs
creates a Carbon object from a timestamp in$milliseconds
format
formats the Carbon object to a string
- Line 6: wrap the length of the song in a Carbon object
- IMPORTANT
- Because there is no Blade helper for Carbon, we need to use the full namespace for the class
Carbon\Carbon::createFromTimestampMs($milliseconds)->format('i:s')
- Another option is to use the
use
statement at the top of the file:@php use Carbon\Carbon; @endphp
and then useCarbon::createFromTimestampMs($milliseconds)->format('i:s')
in the view
- Because there is no Blade helper for Carbon, we need to use the full namespace for the class
php
<tbody>
@foreach($selectedRecord['tracks'] as $track)
<tr class="border-t border-gray-100">
<td class="px-4 py-2">{{ $track['position'] }}</td>
<td class="px-4 py-2">{{ $track['title'] }}</td>
<td class="px-4 py-2">{{ \Carbon\Carbon::createFromTimestampMs($track['length'])->format('i:s') }}</td>
</tr>
@endforeach
<tbody>
@foreach($selectedRecord['tracks'] as $track)
<tr class="border-t border-gray-100">
<td class="px-4 py-2">{{ $track['position'] }}</td>
<td class="px-4 py-2">{{ $track['title'] }}</td>
<td class="px-4 py-2">{{ \Carbon\Carbon::createFromTimestampMs($track['length'])->format('i:s') }}</td>
</tr>
@endforeach
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8