From 0eff434d1d08e25cdca7237439645dc4d8d3bed7 Mon Sep 17 00:00:00 2001 From: Vincent Vanwaelscappel Date: Tue, 16 Apr 2024 11:23:36 +0200 Subject: [PATCH] wip #6864 @0.5 --- .../cart/fluidbook.cart.bastide-resah.js | 508 ++++++++++++++++++ 1 file changed, 508 insertions(+) create mode 100644 js/libs/fluidbook/cart/fluidbook.cart.bastide-resah.js diff --git a/js/libs/fluidbook/cart/fluidbook.cart.bastide-resah.js b/js/libs/fluidbook/cart/fluidbook.cart.bastide-resah.js new file mode 100644 index 00000000..28b4bcc8 --- /dev/null +++ b/js/libs/fluidbook/cart/fluidbook.cart.bastide-resah.js @@ -0,0 +1,508 @@ +function FluidbookCartBastide(cart) { + this.cart = cart; + this.fluidbook = this.cart.fluidbook; + this.data = this.fluidbook.settings.basketReferences; + this.form_endpoint = this.fluidbook.service.getBaseURL()+'bastide'; // Where cart form is processed + this.init(); +} + +FluidbookCartBastide.prototype = { + init: function () { + let $this = this; + this.items = this.fluidbook.cache.get('cart', []); + + // Save changes in quantity field + $(document).on('input', '#Bastide_cart [data-item-quantity]', function () { + let item_index = $(this).data('item-index'); + let quantity = $(this).val(); + + if ($this.items[item_index]) { + $this.items[item_index].quantity = quantity; + $this.save(); + $this.updateCart(); + } else { + console.warn(`Unable to update quantity for cart item ${item_index}`); + } + + return true; + }); + + // Handle "Valider ma sélection" button in cart + $(document).on(this.fluidbook.input.clickEvent, '#Bastide_cart [data-validate-cart]', function(event) { + event.preventDefault(); + + // Use the built-in HTML5 validation to make sure all fields are valid in the cart + let form = $(this).parents('form'); + let isValid = form[0].reportValidity(); + + if (isValid) { + // Originally this was in the link's href but for some reason the validation was + // skipped and the URL change would always occur, even when using + // event.preventDefault() / event.stopImmediatePropagation / event.stopPropagation(); + window.location.hash = '/cart/validate'; + } + }); + + // Handle details form validation and submission + $(document).on(this.fluidbook.input.clickEvent, '#Bastide_user_details [data-send-cart]', function(event) { + event.preventDefault(); + + let form = $('#Bastide_user_details form:visible'); + + if (!form.length) { + console.warn('Error finding form...'); + return false; + } + + // Get the form name so that we can look up the field labels later + let form_name = form.data('name'); + let forms = $this.getForms(); + + // Get the user's details from the form... + let user_data = new FormData(form[0]); + let user_details = {}; // Restructured user data to send to the endpoint + + user_data.forEach(function(value, key) { + user_details[key] = { + label: forms[form_name][key]['label'], + value: value, + }; + }); + + // Prepare the main form data for sending + let data = new FormData(); + + // To make our endpoint more flexible, we need to give it some extra details + data.append('action', 'process-cart'); // There could be other actions handled by the endpoint + data.append('fluidbook_id', $this.fluidbook.settings.id); // In case we need special treatment per Fluidbook + + // Now add the user details to the main form data... + data.append('user_details', JSON.stringify(user_details)); + + // Get data from the cart summary that can be used to generate the XLS + // This is passed as a JSON string to make it easier to handle in the FormData + data.append('cart_items', JSON.stringify($this.getItemsForXLS())); + data.append('column_headings', JSON.stringify($this.getColumnsForXLS())); + + // It's also possible that the Fluidbook will include a querystring parameter (?a=xxxxx) to determine who + // the e-mail should be sent to. This is needs to be passed to the endpoint for handling + let querystring = (new URL(document.location)).searchParams; + data.append('recipient_code', querystring.get('a')); // If the querystring isn't set, it will return null + + // Next, make sure the form is valid. Uses the built-in HTML5 validation + if (form[0].reportValidity()) { + $.ajax({ + url: $this.form_endpoint, + cache: false, + data: data, + processData: false, + contentType: false, + method: 'post', + dataType: 'json', + success: function (data) { + + if (!data.success) { + console.warn('Error processing request', data); + } + + let container = $('#Bastide_user_details'); + // Hide the form and footer content, then show the message from the endpoint + container.find('.details-form, .details-footer').hide(); + container.find('.server-response').text(data.message).show(); + } + }); + } + + event.preventDefault(); + }); + + }, + + // emptyCart: function () { + // this.items = []; + // this.updateCart(); + // this.save(); + // resize(); + // }, + + getCartItemIndex: function(reference) { + let cartItems = this.getItems(); + for (let i = 0; i < cartItems.length; i++) { + if (cartItems[i].reference === reference) { + return i; + } + } + + return -1; + }, + + addToCart: function (reference) { + let existingIndex = this.getCartItemIndex(reference); + + // If this item has already been added to the cart, increment the quantity instead of adding a new item + if (existingIndex >= 0) { + this.items[existingIndex].quantity += 1; + } else { + let item = {}; + item.reference = reference; + item.quantity = 1; // default initial quantity + + // Items are added to the beginning of the array so most recently added are displayed at the top + // This is better than reversing the array when we display it because we use the index for removing items + this.items.unshift(item); + } + + this.save(); + return true; + }, + + removeFromCart: function (index) { + if (index >= 0) { + this.items.splice(index, 1); + this.save(); + } + }, + + save: function () { + this.fluidbook.cache.set('cart', this.getItems()); + this.updateIcon(); + //this.fluidbook.cart.updateLinks(); + }, + + getItems: function () { + let res = []; + let $this = this; + $(this.items).each(function (index, item) { + if (typeof $this.data[item.reference] !== 'undefined') { + res.push(item); + } + }); + return res; + }, + + // Required by FluidbookCart.updateLinks() + getItemsReferences: function () { + return this.getItems(); + }, + + getItemCount: function () { + return this.getItems().length; + }, + + getItemsForXLS: function () { + let $this = this; + let items = []; + + $.each(this.getItems(), function (index, cart_item) { + + let item = {}; + let cart_reference = cart_item['reference']; + let data = $this.data[cart_reference]; // Source data matched from spreadsheet + + $.each($this.getColumnsForXLS(), function (key, title) { + + switch(key) { + case 'QUANTITY': + item[key] = cart_item['quantity']; + break; + default: + item[key] = data[key]; + } + }); + + items.push(item); + }); + + return items; + }, + + updateCart: function () { + if ($('#Bastide_cart').length > 0) { + $('#Bastide_cart .content').html(this.getCartContent()); + this.fluidbook.resize.resize(); + } + }, + + updateIcon: function () { + $(this.fluidbook).trigger('fluidbook.cart.updateIcon', {number: this.getItemCount()}); + }, + + openModal: function (title, content, callback) { + let view = `
+ ${this.fluidbook.menu.getCaption(title)} +
+ ${content} +
+
`; + this.fluidbook.menu.viewWrap(view, 'cart'); + callback(); + }, + + openMenu: function (p1, p2, callback) { + + this.fluidbook.menu.quickCloseView(); + + // Handle the validation screen when accessed via #/cart/validate + if (p1 === 'validate') { + return this.openCartUserDetails(callback); + } + + // The cart opens every time an item is added, so the user can pick a colour and quantity + // These URLs use the #/cart/add/xxxxx URL to pass the reference across + if (p1 === 'add' && p2) { + this.addToCart(p2); + } + return this.openCart(p2, callback); + }, + + openCart: function (p2, callback) { + this.openModal('Ma Sélection', this.getCartContent(), function () { + callback(); + }); + }, + + openCartUserDetails: function (callback) { + $('html').removeClass('cart-form-visible'); // Make sure forms are hidden initially + this.openModal('Mes Coordonnées', this.getCartUserDetailsContent(), function () { + callback(); + }); + }, + + getColumns: function () { + // Map of data key names to their display labels - this controls the order of the columns + // Note: the key names here should match the first row column titles in the spreadsheet + // This can be overridden by specifying the cart_columns in the "Paramètres panier" field (cartExtraSettings) + // The format for the setting is: + // cart_columns=XLS COL NAME|Display label,XLS COL 2|Display label 2 + if (fluidbook.settings.cartColumns) { + return fluidbook.settings.cartColumns; + } + + return { + 'ARTICLE CODE': 'Réf', + 'ARTICLE': 'Article', + 'CONDITIONNEMENT VENTE': 'Conditionnement', + 'QUANTITY': 'Quantité', // Special case: not part of the data + 'DELETE': '', // No column label for delete buttons + }; + }, + + getColumnsForXLS: function () { + // The columns should be the same as for the cart except we skip "DELETE" + let columns = {}; + + $.each(this.getColumns(), function(key, title) { + + if (key === 'DELETE') return; + + columns[key] = title; + }); + + return columns; + }, + + // This cart was based off CFOC's, so we're keeping the structure where it's possible to have multiple forms + getForms: function () { + return { + 'default': { + 'name': { + 'label': "Nom et Prénom", + 'type': 'text', + 'required': true + }, + 'phone': { + 'label': "Téléphone", + 'type': 'text', + 'required': true + }, + 'email': { + 'label': "Email", + 'type': 'email', + 'required': true + }, + 'billing_address': { + 'label': "Adresse de facturation", + 'type': 'textarea', + 'rows': 2, + 'required': false + }, + 'message': { + 'label': "Message", + 'type': 'textarea', + 'rows': 3, + 'required': false + }, + }, + } + }, + + getCartContent: function () { + if (this.getItemCount() === 0) { + return `
${ this.fluidbook.l10n.__('your cart is empty') }
`; + } + + let $this = this; + let columns = this.getColumns(); + + let content = + `
+
+ + + + ${Object.entries(columns) + .map(heading => ``) + .join('') + } + + + `; + + $.each(this.getItems(), function (index, item) { + + content += ''; + + $.each(columns, function (key, title) { + + let data = $this.data[item.reference]; + let value = data[key] || '—'; // Fallback for missing values + let output = ''; + + switch(key) { + + case 'PRIX': + output += $this.formatPrice(value); + break; + case 'QUANTITY': + let min_quantity = 1; + + output = `
`; + + output += ``; + + output += `` + + output += ``; + + output += `
`; // .quantity-controls + + break; + case 'DELETE': + output = ` + ${getSpriteIcon('interface-close')} + `; + break; + default: + output = value; + } + + // The data-name is used as a hook for CSS styles + // The data-label is displayed as the inline label in small screen views + content += ``; + }); + + content += ''; + }); + + content += ''; + content += '
${heading[1]}
${output}
'; + + content += ``; + + content += '
'; + content += '
'; + + return content; + }, + + getCartUserDetailsContent: function() { + + let forms = ''; + + $.each(this.getForms(), function(name, fields) { + forms += `
`; + + $.each(fields, function(field_name, field_details) { + let required = field_details.required ? 'required' : ''; + let label = field_details.required ? `${field_details.label}*` : field_details.label; + let field_ID = `${name}_${field_name}`; + + forms += ``; + + switch (field_details.type) { + case 'textarea': + forms += `` + break; + default: + forms += ``; + } + }); + + forms += `
`; + }); + + let content = + `
+
+ ${forms} +
*Champs obligatoires
+
+ + +
+ `; + + return content; + }, + + getMenuWidth: function () { + return window.location.hash === '#/cart/validate' ? 990 : 1190; // Validate modal is narrower than main cart + }, + + formatPrice: function (price) { + + if (typeof price !== 'number') { + price = this.parseFloat(price); + } + + return price.toLocaleString("fr-FR", { + style: "currency", + currency: "EUR", + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }); + }, + + parseFloat: function (s) { + if (typeof s === 'number') { + return s; + } + if (s === undefined || s === null || s === '') { + return 0; + } + s = s.replace(/\s/g, ''); + return parseFloat(s); + }, +}; -- 2.39.5