import { GetBasketItems, AddProductToBasket, RemoveProductFromBasket, GetBasketTotal, GetBasketTotalPrice } from './basket-popout.mjs'; import { RegisterComponent, Component } from './components.mjs'; class Basket extends Component { static __IDENTIFY() { return 'basket'; } constructor() { super(Basket); } OnMount() { const basket = localStorage.getItem('basket'); if (basket) { const basketItems = JSON.parse(basket); this.setState({ ...basketItems, }, false); } else { this.setState({ items: {}, total: 0, }, false); } } Render() { return { template: /* html */`
Your Basket
Total: {this.state.total} items
${Object.keys(this.state.items).map((key) => { const item = this.state.items[key]; const modifier = key.includes('~'); return /* html */`
 0 in stock
`; }).join('')}
`, style: ` .basket { padding-top: 10px; } .basket-header { display: flex; font-size: 2em; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #ccc; } .basket-item { display: flex; flex-direction: row; align-items: center; } .basket-item-listing { font-size: 1.3em; flex-basis: 65%; flex-grow: 4; } @media (pointer:none), (pointer:coarse), screen and (max-width: 900px) { .basket-item { flex-direction: column; } .basket-item-listing { font-size: 1.3em; width: 100%; flex-basis: 100%; flex-grow: 4; } } .basket-item-content { flex-basis: 35%; flex-grow: 1; width: 100%; margin: 0 auto; display: flex; flex-direction: row; justify-content: space-around; align-items: center; } .product-quantity-selector { display: flex; flex-direction: row; justify-content: center; align-items: center; font-size: 1em; } .product-quantity-button { cursor: pointer; font-size: 1.7em; border: #1A1A1A solid 1px; background-color: #F5F6F6; border-radius: 0.2em; width: 1.5em; height: 1.5em; } .remove-quantity { font-size: 1em; width: fit-content; } /* https://www.w3schools.com/howto/howto_css_hide_arrow_number.asp */ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } input[type=number] { -moz-appearance: textfield; } .quantity-input { height: 2.2em; width: 3.7em; background-color: #F5F6F6; border-top: #1A1A1A solid 1px; border-bottom: #1A1A1A solid 1px; border-right: none; border-left: none; text-align: center; font-size: 1.2em; } .basket-footer { display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 10px; border-top: 1px solid #ccc; } .basket-footer-subtotal { font-size: 1.5em; } .basket-footer-checkout { flex-basis: 70%; display: flex; flex-direction: row; justify-content: flex-end; align-items: center; } .basket-footer-button { cursor: pointer; background-color: #222; border: none; color: #F5F6F6; padding: 0.5em; margin-left: 1em; font-size: 1.4em; transition: all 0.2s ease-in; } .basket-footer-button:hover { background-color: #F5F6F6; outline: 2px solid #222; color: #222; } `, }; } async UpdateSubtotal() { const subtotal = await GetBasketTotalPrice(); const basketSubtotal = this.root.querySelector('.basket-subtotal'); if (basketSubtotal) { basketSubtotal.innerText = parseFloat(subtotal).toFixed(2); } } OnRender() { this.UpdateSubtotal(); this.root.querySelectorAll('.basket-item-listing').forEach((listing) => { // listen to mutations on the attribute stock because the stock is updated once the // super compact listing is loaded and the stock is updated const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.attributeName === 'stock') { const stock = parseInt(mutation.target.getAttribute('stock')); const itemCompositeId = mutation.target.getAttribute('id') + (mutation.target.getAttribute('modifier') ? '~' + mutation.target.getAttribute('modifier') : ''); const item = this.state.items[itemCompositeId]; if (item) { item.stock = stock; this.setState({ items: { ...this.state.items, [itemCompositeId]: item, }, ...this.state, }, false); } // update the stock number const stockNumber = mutation.target.parentElement.querySelector('.stock-number'); stockNumber.innerText = stock; const stockMax = mutation.target.parentElement.querySelector('.quantity-input'); stockMax.setAttribute('max', stock); } }); }); observer.observe(listing, { attributes: true, attributeFilter: ['stock'], }); }); // set up each button to update the quantity and remove if it is zero this.root.querySelectorAll('.product-quantity-button').forEach((button) => { button.addEventListener('click', (event) => { let clickedItem = event.target.parentElement; let listing = clickedItem.querySelector('.basket-item-listing'); if (!listing) { clickedItem = clickedItem.parentElement; listing = clickedItem.querySelector('.basket-item-listing'); } const id = listing.getAttribute('id'); const modifier = listing.getAttribute('modifier'); const compositeId = id + (modifier ? `~${modifier}` : ''); const item = this.state.items[compositeId]; // update the quantity if (event.target.classList.contains('reduce-quantity')) { if (item.quantity > 0) { item.quantity--; RemoveProductFromBasket(id, item.type, 1, modifier); } if (item.quantity === 0) { RemoveProductFromBasket(id, item.type, item.quantity, modifier); this.UpdateSubtotal(); return this.setState({ ...this.state, total: GetBasketTotal(), items: { ...GetBasketItems(), }, }); } } else if (event.target.classList.contains('increase-quantity')) { if (item.quantity < item.stock) { item.quantity++; AddProductToBasket(id, item.type, 1, modifier); } } else if (event.target.classList.contains('remove-quantity')) { RemoveProductFromBasket(id, item.type, item.quantity, modifier); this.UpdateSubtotal(); return this.setState({ ...this.state, total: GetBasketTotal(), items: { ...GetBasketItems(), }, }); } // update the total this.setState({ ...this.state, total: GetBasketTotal(), items: { ...this.state.items, [compositeId]: item, }, }, false); // update the total so it doesn't need to be rendered again const total = this.root.querySelector('.basket-total'); total.innerText = this.state.total; // and the same on the item const itemTotal = clickedItem.querySelector('.quantity-input'); itemTotal.value = parseInt(item.quantity); this.UpdateSubtotal(); }); }); // do the same but with the input field this.root.querySelectorAll('.quantity-input').forEach((input) => { input.addEventListener('change', (event) => { let clickedItem = event.target.parentElement; let listing = clickedItem.querySelector('.basket-item-listing'); if (!listing) { clickedItem = clickedItem.parentElement; listing = clickedItem.querySelector('.basket-item-listing'); } const id = listing.getAttribute('id'); const modifier = listing.getAttribute('modifier'); const compositeId = id + (modifier ? `~${modifier}` : ''); const item = this.state.items[compositeId]; // update the quantity if (event.target.value < 0) { event.target.value = 0; } if (event.target.value > item.stock) { event.target.value = item.stock; } if (parseInt(event.target.value) === 0) { RemoveProductFromBasket(id, item.type, item.quantity, modifier); return this.setState({ ...this.state, total: GetBasketTotal(), items: { ...GetBasketItems(), }, }); } // if has gone up in quantity then add it if (item.quantity < event.target.value) { AddProductToBasket(id, item.type, event.target.value - item.quantity, modifier); item.quantity = parseInt(event.target.value); } else if (item.quantity > event.target.value) { RemoveProductFromBasket(id, item.type, item.quantity - event.target.value, modifier); item.quantity = parseInt(event.target.value); } this.UpdateSubtotal(); // update the total this.setState({ ...this.state, total: GetBasketTotal(), items: { ...this.state.items, [compositeId]: item, }, }, false); // update the total so it doesn't need to be rendered again const total = this.root.querySelector('.basket-total'); total.innerText = this.state.total; }); }); } } RegisterComponent(Basket);