Basket
- In previous section we used a preliminary basket view (with only 3 fixed records) to test our
Cart
class - Now we we'll make this a 'real' basket where we can:
- Add/remove items from the basket
- Empty our basket
- Place an order and add all items (in the basket) to the database
Add a record to the cart
Change 'Add to cart' link
- The record detail page is the only place where we can add a record to our basket
- Open resources/views/shop/show.blade.php
- Update the path (
href
) behind the Add to cart link
- Update the path (
<p>
<a href="/basket/add/{{ $record->id }}" 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>
1
2
3
4
5
6
2
3
4
5
6
- For a better UX, we can change the cursor if the link button is disabled
REMARKS
- Open developer tools in Chrome and select an 'Add to cart' link button with the class
disabled
(choose a record that is out of stock) - Bootstrap adds the CSS rule
pointer-events: none;
to such a link button with the classdisabled
- This rule prevents the normal event (the link) to work and that's exactly what we want
- The disadvantage of this rule however is that you can't change the cursor!
- The solution is to change the cursor on the parent element with a bit of JavaScript
- Open resources/js/VinylShop.js and change the cursor on the parent element of any element with classes
btn
anddisabled
$(function(){
...
$('.btn.disabled').parent().css('cursor', 'not-allowed');
});
1
2
3
4
2
3
4
Send feedback to the user
- Open app/Http/Controllers/BasketController.php and flash a message to the view (that already contains the sub-view shared/alert.blade.php to show such flash messages)
public function addToCart($id)
{
$record = Record::findOrFail($id);
$record->cover = $record->cover ?? "https://coverartarchive.org/release/$record->title_mbid/front-250.jpg";
Cart::add($record);
session()->flash('success', "The record <b>$record->title</b> from <b>$record->artist</b> has been added to your basket");
return back();
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Update the view
- Open resources/views/basket.blade.php
- The login inside the basket view is as follows:
- If the cart is empty: show a message (that the cart is empty)
- If the cart is not empty:
- Show all the items in a responsive table
- Provide a link for each item to increase/decrease the number of items
- Show the total price of all items in your basket
- If the user is logged in, show a button to actually place the order
- If the user is not logged in, show a message that he must login/register first
Show message if cart is empty
- Remove all debug content inside the
main
section and add the following code:
@section('main')
<h1>Basket</h1>
@if( Cart::getTotalQty() == 0)
<div class="alert alert-primary">
Your basket is empty.
</div>
@else
<!-- Cart comes here -->
@endif
@endsection
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Show cart info
- Update the code if the cart is not empty
@else
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Qty</th>
<th>Price</th>
<th></th>
<th>Record</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach(Cart::getRecords() as $record)
<tr>
<td>{{ $record['qty'] }}</td>
<td>€ {{ $record['price'] }}</td>
<td>
<img class="img-thumbnail cover" src="/assets/vinyl.png"
data-src="{{ $record['cover'] }}"
alt="{{ $record['title'] }}">
</td>
<td>
{{ $record['artist'] . ' - ' . $record['title'] }}
</td>
<td>
<div class="btn-group btn-group-sm">
<a href="/basket/delete/{{ $record['id'] }}" class="btn btn-outline-secondary">-1</a>
<a href="/basket/add/{{ $record['id'] }}" class="btn btn-outline-secondary">+1</a>
</div>
</td>
</tr>
@endforeach
<tr>
<td></td>
<td></td>
<td></td>
<td>
<p><a href="/basket/empty" class="btn btn-sm btn-outline-danger">Empty your basket</a></p>
</td>
<td>
<p><b>Total</b>: € {{ Cart::getTotalPrice() }}</p>
<p><a href="/user/checkout" class="btn btn-sm btn-outline-success">Checkout</a></p>
</td>
</tr>
</tbody>
</table>
</div>
@endif
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
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
WARNING
- When we loop over all our records inside the cart, the variable
$record
is a normal array, not a collection- To access a value inside this array, you can only use the default array syntax like
$record['qty']
and not the collection syntax$record->qty
!
- To access a value inside this array, you can only use the default array syntax like
REMARK
- Everything works, except the Checkout link because we haven't provided a route yet
Update the UI
- Because Bootstrap does not provide any classes to format a table properly, we will first create some classes for giving elements a fixed width ourselves
- Open resources/sass/_main.scss and generate some width-xxx classes
@for $i from 1 through 20 {
// .width-10, .width-20, ... .width-200,
.width-#{$i * 10} {
width: #{$i * 10}px;
}
}
1
2
3
4
5
6
2
3
4
5
6
- The covers are much too big compared to the rest of the table
- We add some .width-xxx classes to reduce the width of the quantity, price, cover and increase/decrease columns and give the remaining width to the record cells
<thead>
<tr>
<th class="width-50">Qty</th>
<th class="width-80">Price</th>
<th class="width-80"></th>
<th>Record</th>
<th class="width-120"></th>
</tr>
</thead>
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- Add a
script_after
section to the page- Replace the dummy cover with the real cover
- It's also nice if we vertically center all content inside the table cells with the Bootstrap class
align-middle
- We (vertically) center all the cells inside
tbody
, except inside the last row
- We (vertically) center all the cells inside
@section('script_after')
<script>
$(function () {
$('.cover').each(function () {
$(this).attr('src', $(this).data('src'));
});
$('tbody tr:not(:last-child) td').addClass('align-middle');
});
</script>
@endsection
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Show/hide checkout link
- Update the checkout link so that it's only visible if a user is logged in
<td>
<p><b>Total</b>: € {{ Cart::getTotalPrice() }}</p>
@auth()
<p><a href="/user/checkout" class="btn btn-sm btn-outline-success">Checkout</a></p>
@endauth
</td>
1
2
3
4
5
6
2
3
4
5
6
- Show, just before the table, a warning/alert that you have to login before you can checkout
@guest()
<div class="alert alert-primary">
You must be <a href="/login"><b>logged in</b></a> to checkout
</div>
@endguest
<div class="table-responsive">
...
</div>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
EXERCISE: Remove one record
- Add an extra button to remove a whole record (all copies) from the cart
<div class="btn-group btn-group-sm">
<a href="/basket/delete/{{ $record['id'] }}" class="btn btn-outline-secondary">-1</a>
<a href="/basket/add/{{ $record['id'] }}" class="btn btn-outline-secondary">+1</a>
<a href="/basket/remove/{{ $record['id'] }}" class="btn btn-outline-secondary">
<i class="fas fa-trash-alt"></i>
</a>
</div>
1
2
3
4
5
6
7
2
3
4
5
6
7
- Add a new method
removeRecord($item)
to theCart
class to remove the whole record from the$records
array
(Don't forget to renew the ide-helper!) - Add a new method
removeRecordFromCart($id)
to theBasketController
- Select the record in the database
- Pass it to the
removeRecord()
method inside theCart
class - Redirect back to the basket view
- Add the new route to routes/web.php