Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Add To Cart

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

The cart data is being loaded via AJAX... and we need that data before we can add a new item to the cart. It should all load pretty quickly, but to be safe, let's prevent the user from clicking the "Add to cart" button until that AJAX call is done.

Head up to find that button. This is delightfully simply: add :disabled set to cart === null.

<template>
<div>
... lines 3 - 8
<div
... lines 10 - 12
>
... lines 14 - 30
<div class="col-8 p-3">
... lines 32 - 33
<div class="row mt-4 align-items-center">
... lines 35 - 38
<div class="col-8 p-3">
<div class="d-flex align-items-center justify-content-center">
... lines 41 - 50
<button
class="btn btn-info btn-sm"
:disabled="cart === null"
>
Add to Cart
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
... lines 64 - 132

If we don't have a cart, no clicky the button! It's disabled.

On Click, Add to Cart

Now let's hook up the real functionality: @click="" and call a new addToCart() method:

<template>
<div>
... lines 3 - 8
<div
... lines 10 - 12
>
... lines 14 - 30
<div class="col-8 p-3">
... lines 32 - 33
<div class="row mt-4 align-items-center">
... lines 35 - 38
<div class="col-8 p-3">
<div class="d-flex align-items-center justify-content-center">
... lines 41 - 50
<button
class="btn btn-info btn-sm"
:disabled="cart === null"
@click="addToCart"
>
Add to Cart
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
... lines 65 - 142

Head down to the component, add methods: {}, then addToCart(). This won't need any arguments.

... lines 1 - 65
<script>
... lines 67 - 73
export default {
name: 'ProductShow',
... lines 76 - 113
methods: {
addToCart() {
... lines 116 - 120
},
},
};
</script>
... lines 125 - 142

Inside, we can use an addItemToCart method from cart-service. I'll type addItemToCart() and hit tab to auto-complete... because that little trick gets PhpStorm to add the import for me.

Ok, the first argument is the cart object - so this.cart. In practice, we know that it's safe to reference this.cart because our addToCart method can't be called until after the cart AJAX call has finished. Until then, the button is disabled.

... lines 1 - 65
<script>
import { fetchCart, addItemToCart } from '@/services/cart-service.js';
... lines 68 - 73
export default {
name: 'ProductShow',
... lines 76 - 113
methods: {
addToCart() {
addItemToCart(this.cart, {
... lines 117 - 119
});
},
},
};
</script>
... lines 125 - 142

The second argument is the item to add. This is an object with three things: product - set to the product IRI, so this.product['@id'] - color - which for right now, I'm going to set to null - and quantity set to 1.

... lines 1 - 65
<script>
import { fetchCart, addItemToCart } from '@/services/cart-service.js';
... lines 68 - 73
export default {
name: 'ProductShow',
... lines 76 - 113
methods: {
addToCart() {
addItemToCart(this.cart, {
product: this.product['@id'],
color: null,
quantity: 1,
});
},
},
};
</script>
... lines 125 - 142

If... you're thinking:

How did Ryan magically know what keys to pass here?

That's... a fair question. This is just how the API is designed. If you go back to the API docs... and open the put endpoint, the example shows the keys we're using. Both of those strings are IRI strings.

Anyways... I think it's testing time! I'll refresh to be sure... hit "Add to Cart" and... I think it worked? I don't see any errors... but I can see the successful AJAX call to /api/carts. It's a POST request because this is creating a new cart.

Let's see if the cart data updated inside Vue. Hmm, it did not! This is a quirk that is completely unrelated to Vue... but I wanted to show in case it happens to you. If we look at the HTML source, we can see that window.cartIri is still null. The problem is that I use localhost:8000 for tons of projects, which means I already have some session cookies from those other projects. And since I'm using http instead of https this time, my browser is refusing to override my old secure cookie with an insecure one.

The fix is to go to Application and clear the storage to get rid of any cookies from other projects.

Let's try this again. Refresh... go back to the Vue dev tools and click "Add to Cart". This time when I refresh... yea! Look at the header! It says: "Shopping cart (1)". And if we look at the Vue tools... yes! The cart contains 1 item with quantity 1.

To prove we can increase the quantity, hit "Add to Cart" 2 more times. Notice that the header does not instantly update. That's no surprise, but we will fix that. Refresh to see the new value and... yes! It says 3! Our cart still only has one item, but with quantity 3.

So.. yay! We can add things to our cart! Next, let's make this whole process fancier with some animations and hook up the quantity input.

Leave a comment!

4
Login or Register to join the conversation

Hi,
Currently adding item to cart cause an issue.

Request URL: http://localhost:8000/api/carts
Request Method: POST
Status Code: 400 Bad Request
Payload : {"items":[{"product":{"@context":"/api/contexts/Product","@id":"/api/products/1","@type":"Product","id":1,"name":"Floppy disk","description":"With 1.44 mb of storage space enjoy your favorite thumbnail photo on the go!","brand":"Low End Luxury","price":1700,"stockQuantity":15,"category":"/api/categories/1","colors":[],"image":"/uploads/products/floppy-disc.png"},"color":null,"quantity":1}]}
Log : Uncaught PHP Exception Symfony\Component\Serializer\Exception\NotNormalizableValueException: "Update is not allowed for this operation." at ../code-vue/start/vendor/api-platform/core/src/Serializer/ItemNormalizer.php line 56

I don't understand why, Cart seems well configured in ApiPlatform.

Reply

Hey @atournayre!

Hmmm. Are you getting this directly from the downloaded course code? Or trying things on a fresh project with newer dependencies? That line - 56 - from ItemNormalizer might be getting triggered via the individual product being denormalized. I see that we're sending id: 1 in the JSON. If you remove that before making the AJAX call, does that make things all better?

On a high level, that IS the part that looks odd. We're sending a product set to an object with @id AND individual fields. Usually, you do one or the other: set product to an IRI string to tell AI Platform to use that existing product or set it to an object of data without an @id, which indicates that a new product should be created. Iirc, in the tutorial, we set product to its @id string - you can see that in this code block - https://symfonycasts.com/screencast/vue2/add-to-cart#codeblock-a668af6a6e - notice we're passing this.product['@id'].

Let me know if that helps :).

Cheers!

Reply
Robin Avatar

my phpstorm keeps doing stuff like this for auto importing. how do i fix it so that it will use the @ for home directory and add space around curly braces?

import {addItemToCart} from "../services/cart-service";

Reply

Hey Robin,

Probably take a look at Preferences in your PhpStorm, I don't know if that may be configured, but if it may be - it should be in preferences then. On mac, you can press "Commad" + "," shortcut, or go to PhpStorm menu -> Preferences.

I hope this helps.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This course is also built to work with Vue 3!

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@fortawesome/fontawesome-free": "^5.15.1", // 5.15.1
        "@symfony/webpack-encore": "^0.30.0", // 0.30.2
        "axios": "^0.19.2", // 0.19.2
        "bootstrap": "^4.4.1", // 4.5.3
        "core-js": "^3.0.0", // 3.6.5
        "eslint": "^6.7.2", // 6.8.0
        "eslint-config-airbnb-base": "^14.0.0", // 14.2.0
        "eslint-plugin-import": "^2.19.1", // 2.22.1
        "eslint-plugin-vue": "^6.0.1", // 6.2.2
        "regenerator-runtime": "^0.13.2", // 0.13.7
        "sass": "^1.29.0", // 1.29.0
        "sass-loader": "^8.0.0", // 8.0.2
        "vue": "^2.6.11", // 2.6.12
        "vue-loader": "^15.9.1", // 15.9.4
        "vue-template-compiler": "^2.6.11", // 2.6.12
        "webpack-notifier": "^1.6.0" // 1.8.0
    }
}
userVoice