el: '#app',
data: {
+ items: {}, // Populated from data attribute on root element so we can pass data from PHP
+ },
+ beforeMount: function () {
+ this.items = JSON.parse(this.$el.dataset.cartItems);
+ },
+
+ computed: {
+ cartItemCount() {
+ // Todo: See if this should count just number of items or make a sum of all quantities? What is more useful? The sum of quantities can be found using map/reduce functions but this needs to be adapted for the object structure using Object.keys as the source.
+ return Object.keys(this.items).length;
+ }
},
methods: {
// Todo: find a better way to structure this with Vue
openCart() {
document.body.classList.add('cart-open');
+ },
+ closeCart() {
+ document.body.classList.remove('cart-open');
+ },
+ updateItemQuantity(update) {
+ this.items[update.id].quantity = update.quantity;
}
+
}
<template>
<div class="cart-wrapper">
- <cart-item v-for="item in items" :key="item.id" :item="item"></cart-item>
+ <cart-item v-for="item in items" :key="item.id" :item="item" @update-item-quantity="updateItemQuantity"></cart-item>
</div>
</template>
props: {
items: {
- type: Array
+ type: Object,
+ required: true,
}
+ },
+
+ methods: {
+ // Todo: consider event bus to avoid having to pass this up through all the levels?
+ updateItemQuantity(update) {
+ this.$emit('update-item-quantity', update)
+ },
}
}
</script>
</div>
<div class="bg-grey-100 py-1 pl-3 my-2 flex items-center justify-between">
<span class="mr-2">Quantité</span>
- <number-input :value="item.quantity" :min="1" inline center controls></number-input>
+ <number-input :value="item.quantity" :min="1" inline center controls @change="updateQuantity"></number-input>
</div>
- <a href="#" class="cart-delete-item text-red">
+ <a href="#" class="cart-delete-item text-red" @click.prevent="deleteItem">
Supprimer
</a>
</div>
item: {
type: Object
}
+ },
+
+ methods: {
+ updateQuantity(newValue, oldValue) {
+ this.$emit('update-item-quantity', {
+ id: this.item.id,
+ quantity: newValue,
+ });
+ },
+
+ deleteItem() {
+ this.$emit('deleteItem', this.item.id);
+ }
}
}
</script>
&-flag
@apply inline-block mr-2
- &-list
+ &-list-wrapper
@apply absolute w-full -mb-1
left: 0
bottom: 100%
- opacity: 0
- transform: translateY(1.5em)
- transition: all 0.1s ease-out
+ overflow: hidden // Child element is translated out of the visible area to give precise clipping
+ &-list
+ transform: translateY(100%)
+ transition: transform 0.2s ease-out
li:first-child a
@apply rounded-t
<body class="font-body text-grey-dark">
@include('cubist::body.begin')
-<div id="app" class="flex flex-col min-h-screen">
+
+@php
+ //#### Generate temporary cart data
+ $cart_items = [];
+ for ($i = 1; $i <= 6; $i++) {
+ $cart_items[$i] = [
+ 'id' => $i,
+ 'quantity' => rand(1, 15),
+ 'name' => 'Modèle '. rand(1000, 1500),
+ 'category' => 'Capteur de force',
+ 'image' => '/storage/products/'. rand(1,6) .'.png',
+ ];
+ }
+@endphp
+
+<div id="app" class="flex flex-col min-h-screen" data-cart-items='@json($cart_items)'>
@include('partials.header')
</main>
@include('partials.footer')
+
+ <div class="body-overlay" @click="closeCart"></div>
</div>
-<div class="body-overlay"></div>
<script src="{{ mix('/js/app.js') }}"></script>
<span class="cart-header-title">My Selection</span>
-<span class="cart-header-icon">0</span>
+<span class="cart-header-icon" v-html="cartItemCount"></span>
<span class="footer-locales-current pr-12">
<img src="{{ asset('images/locale-fr.svg') }}" alt="Français" class="footer-locales-flag">Français
</span>
- <ul class="footer-locales-list">
- <li><a href="/de"><img src="{{ asset('images/locale-de.svg') }}" alt="Deutsch" class="footer-locales-flag">Deutsche</a></li>
- <li><a href="/en"><img src="{{ asset('images/locale-en.svg') }}" alt="English" class="footer-locales-flag">English</a></li>
- </ul>
+ <div class="footer-locales-list-wrapper">
+ <ul class="footer-locales-list">
+ <li><a href="/de"><img src="{{ asset('images/locale-de.svg') }}" alt="Deutsch" class="footer-locales-flag">Deutsche</a></li>
+ <li><a href="/en"><img src="{{ asset('images/locale-en.svg') }}" alt="English" class="footer-locales-flag">English</a></li>
+ </ul>
+ </div>
</div>
{{-- Footer Nav Links --}}
<portal-target name="nav-search-toggle" slim></portal-target>
</div>
- {{-- TODO: Make a Vue component for the cart + popout using portals and slots (see Search.vue) --}}
-
<div class="cart-header text-right flex items-center cursor-pointer hover:text-blue" @click="openCart">
@include('partials.cart')
</div>
{{ __('My Selection') }}
</span>
- <a href="#" class="close-cart-popout text-white hover:text-blue">
+ <a href="#" class="close-cart-popout text-white hover:text-blue" @click.prevent="closeCart">
@svg('icon-close-thin')
</a>
</div>
<div class="cart-header-popout-content text-navy font-body p-1v pb-0">
-
- @php
- //#### Generate temporary data
- $cart_items = [];
- for ($i = 1; $i <= 6; $i++) {
- $cart_items[] = [
- 'id' => $i,
- 'quantity' => rand(1, 15),
- 'name' => 'Modèle '. rand(1000, 1500),
- 'category' => 'Capteur de force',
- 'image' => '/storage/products/'. rand(1,6) .'.png',
- ];
- }
- @endphp
-
- <cart :items='@json($cart_items)'></cart>
-
+ <cart :items='items' @update-item-quantity="updateItemQuantity"></cart>
</div>
<div class="cart-header-popout-footer bg-grey-100 p-1v">
<link-button class="block text-center">Obtenir un devis</link-button>