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.form_endpoint = this.fluidbook.service.getBaseURL() + 'bastide'; // Where cart form is processed
this.init();
}
});
// Handle "Valider ma sélection" button in cart
- $(document).on(this.fluidbook.input.clickEvent, '#Bastide_cart [data-validate-cart]', function(event) {
+ $(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
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 () {
// resize();
// },
- getCartItemIndex: function(reference) {
+ getCartItemIndex: function (reference) {
let cartItems = this.getItems();
for (let i = 0; i < cartItems.length; i++) {
if (cartItems[i].reference === reference) {
$.each($this.getColumnsForXLS(), function (key, title) {
- switch(key) {
+ switch (key) {
case 'QUANTITY':
item[key] = cart_item['quantity'];
break;
// The columns should be the same as for the cart except we skip "DELETE"
let columns = {};
- $.each(this.getColumns(), function(key, title) {
+ $.each(this.getColumns(), function (key, title) {
if (key === 'DELETE') return;
getCartContent: function () {
if (this.getItemCount() === 0) {
- return `<div class="cart-empty">${ this.fluidbook.l10n.__('your cart is empty') }</div>`;
+ return `<div class="cart-empty">${this.fluidbook.l10n.__('your cart is empty')}</div>`;
}
let $this = this;
let columns = this.getColumns();
let content =
- `<div class="inner-content">
+ `<div class="inner-content">
<form>
<table id="bastide-cart-table" class="cart-items">
<thead style="text-transform:uppercase">
<tr>
${Object.entries(columns)
- .map(heading => `<th data-name="${heading[0]}">${heading[1]}</th>`)
- .join('')
- }
+ .map(heading => `<th data-name="${heading[0]}">${heading[1]}</th>`)
+ .join('')
+ }
</tr>
</thead>
<tbody>`;
let value = data[key] || '—'; // Fallback for missing values
let output = '';
- switch(key) {
+ switch (key) {
case 'PRIX':
output += $this.formatPrice(value);
case 'QUANTITY':
let min_quantity = 1;
- output = `<div class="quantity-controls">`;
+ output = `<div class="quantity-controls">`;
output += `<button onclick="this.parentNode.querySelector('input[type=number]').stepDown();
this.parentNode.querySelector('input[type=number]').dispatchEvent(new Event('input', { bubbles: true }));
return content;
},
- getCartUserDetailsContent: function() {
+ getCartUserDetailsContent: function () {
let forms = '';
- $.each(this.getForms(), function(name, fields) {
+ $.each(this.getForms(), function (name, fields) {
forms += `<form data-name="${name}">`;
- $.each(fields, function(field_name, field_details) {
+ $.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}`;
});
let content =
- `<div class="inner-content" id="Bastide_user_details">
+ `<div class="inner-content" id="Bastide_user_details">
<div class="details-form">
${forms}
<div class="required-fields-notice">*Champs obligatoires</div>
--- /dev/null
+@breakpoint_table: ~"(max-width: 700px)";
+
+@import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@400;700&display=swap');
+
+// Nav override
+.icon-cart {
+ // Display the tooltip label before the cart icon
+ &:before {
+ content: attr(data-tooltip);
+ display: inline-block;
+ text-transform: uppercase;
+ font-size: 12px;
+ font-weight: bold;
+ font-family: 'Roboto Condensed', sans-serif;
+ letter-spacing: 1px;
+ margin-left: 17px;
+ margin-right: -14px;
+ vertical-align: text-bottom;
+ }
+
+ span.number {
+ background-color: #E41C38;
+ top: 1em;
+ right: 1em;
+ }
+}
+
+// Cart modals
+#Bastide_cart {
+ background-color: #fff;
+ color: #000;
+ height: 100%;
+ min-height: 35vh;
+ min-width: 320px;
+ font-size: 18px;
+
+ @media (max-width: 1060px) {
+ font-size: 15px;
+ }
+ @media @breakpoint_table {
+ font-size: 1rem; // Once we're in the stacked view, the font can be a normal size again
+ }
+ @media (max-width: 450px) {
+ font-size: 14px;
+ }
+
+ .caption {
+ padding: 30px 20px;
+ height: auto;
+ }
+
+ #mview-dialog-title {
+ font-size: 16px;
+ font-weight: bold;
+ text-transform: uppercase;
+ }
+
+ > .content .inner-content {
+ width: 94%;
+ margin: 0 auto;
+ position: relative;
+
+ @media @breakpoint_table {
+ width: 90%;
+ }
+ }
+
+ // Main table
+ table.cart-items {
+ width: 100%;
+ max-width: none;
+ margin: 0;
+ border-collapse: collapse;
+ font-size: 0.75em;
+
+ thead {
+ background-color: @menu-background;
+
+ @media @breakpoint_table {
+ display: none;
+ }
+ }
+
+ th {
+ color: #fff;
+ font-weight: bold;
+ text-align: left;
+ }
+
+ th, td {
+ padding: 1.5em 1.75em;
+
+ @media (max-width: 1060px) {
+ padding: 1em 0.5em;
+ }
+ }
+
+ tbody tr {
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.05);
+ }
+
+ @media @breakpoint_table {
+ display: block;
+ padding-top: 1em;
+ border-top: 1px solid;
+ }
+ }
+
+ // Collapse table cells and display header labels before items
+ tbody td {
+ white-space: normal;
+
+ @media @breakpoint_table {
+ display: flex;
+ align-items: center;
+ text-align: left;
+ padding-top: 0;
+
+ &:before {
+ content: attr(data-label) ' : ';
+ font-weight: bold;
+ flex: 0 0 9.5em; // Set width of "label" column in stacked mode
+ text-align: right;
+ padding-right: 10px;
+ line-height: 1;
+ white-space: nowrap;
+ }
+ }
+ }
+ }
+
+ // Modal close button
+ .caption .back {
+ background-color: transparent;
+ }
+
+ [data-name="QUANTITY"] {
+ user-select: none; // Stop text being selected when clicking quickly on -/+ buttons
+ }
+
+ .quantity-controls {
+ display: flex;
+ align-items: center;
+ }
+
+ .quantity-button {
+ -webkit-appearance: none;
+ appearance: none;
+ border: none;
+ background: transparent;
+ padding: 0.25em 0.5em;
+ cursor: pointer;
+ height: 16px; // For better vertical alignment with input
+ box-sizing: content-box;
+
+ svg {
+ height: 100%;
+ width: auto;
+ }
+ }
+
+ // Cart item delete buttons
+ [data-name="DELETE"] {
+
+ a {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background: #E41C38;
+ width: 2em;
+ height: 2em;
+ }
+
+ svg {
+ width: 0.9em;
+ fill: #fff;
+ }
+
+ @media @breakpoint_table {
+ &:before {
+ content: '' !important; // No label in stacked mode for this item
+ }
+
+ a {
+ width: auto;
+ color: #fff;
+ padding: 0 1em;
+
+ &:before {
+ content: attr(title);
+ text-transform: uppercase;
+ margin-right: 1em;
+ }
+ }
+
+ }
+
+ }
+
+ input[type=email], input[type=number], input[type=text], select, textarea {
+ -webkit-appearance: none;
+ appearance: none;
+ border: 2px solid;
+ border-radius: 0;
+ padding: 0.25em 0.5em;
+ font-size: 1em;
+ width: 100%;
+ }
+ input, select, textarea {
+ background-color: #fff;
+ font-family: inherit;
+ outline: none;
+ }
+
+ select {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13.4 8.1' xml:space='preserve'%3E%3Cpath d='m.7.7 6 6 6-6' fill='none' stroke='%231c1c1c' stroke-width='2'/%3E%3C/svg%3E");
+ background-position: calc(100% - 0.6em) 50%;
+ background-repeat: no-repeat;
+ background-size: 0.85em auto;
+ padding-right: 1.8em;
+
+ &.alert, &:invalid { // Warning for unset colour selection
+ color: red;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13.4 8.1' xml:space='preserve'%3E%3Cpath d='m.7.7 6 6 6-6' fill='none' stroke='red' stroke-width='2'/%3E%3C/svg%3E");
+ }
+
+ @media @breakpoint_table {
+ width: max-content; // When switching to stacked view, select boxes should only be as wide as their content
+ }
+ }
+
+ // Quantity inputs - hide browser up/down arrows
+ input::-webkit-outer-spin-button,
+ input::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+ }
+
+ input[type=number] {
+ -moz-appearance: textfield;
+ }
+
+ input[data-item-quantity] {
+ width: 2.5em;
+ text-align: center;
+ padding: 0.25em 0.25em;
+ }
+
+ // Cart footer
+ tfoot.cart-footer {
+
+ // Extra spacing between table footer and table body
+ // Margins don't work in the middle of a table and we need to ensure the columns structure remains consistent
+ &:before {
+ content: '';
+ display: table-row;
+ height: 1em;
+ }
+
+ // Border + extra padding on the first row of cells
+ // Once again, this is more convoluted because we can't use margins in the table
+ tr:first-of-type td {
+ padding-top: 1.5em;
+ border-top: 2px solid;
+ }
+
+ td {
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ text-align: right;
+ }
+
+ // When cart popup goes into "full screen" / cover mode
+ [data-menu="cart"].fs & {
+ top: unset;
+ position: unset; // This needs to be unset so .exclusivity-notice remains relative to .content-inner
+ }
+
+ // Responsive overrides for small screens
+ @media @breakpoint_table {
+ tr {
+ display: flex;
+ }
+
+ td {
+ // Middle columns that contain labels and totals
+ // The fixed size makes them centred, while other cells flex to take space on left and right
+ // Their size is set in em units so that it works regardless of the actual font size
+ flex: 0 0 7.5em; // The flex-basis might need to be adjusted if labels or values get any wider
+
+ &:first-child, &:last-child {
+ flex: 1;
+ }
+ }
+ }
+
+ }
+
+ .fonctions {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 1em;
+ padding: 2em 0;
+
+ // Reorganise footer for better display and wrapping
+ @media @breakpoint_table {
+ flex-wrap: wrap;
+ flex-direction: row-reverse;
+ justify-content: flex-start;
+ }
+
+ a {
+ background-color: @menu-background;
+ color: #fff;
+ white-space: nowrap;
+ margin: 0;
+
+ @media @breakpoint_table {
+ flex: 1; // Allows items to grow to maximise width
+ }
+ }
+ }
+
+ .validate-cart, .send-cart {
+ min-width: 30%;
+ font-weight: bold;
+
+ @media @breakpoint_table {
+ min-width: unset; // Without resetting this, the flex items weren't wrapping
+ }
+ }
+
+ //==== User details modal
+
+ #Bastide_user_details {
+ width: 90% !important;
+
+ .details-form {
+ font-size: 0.75em;
+ }
+
+ form {
+ max-width: 680px;
+ margin-left: auto;
+
+ display: grid;
+ /* 1st column should be max-content but it can shrink if needed... */
+ /* 2nd column should take remaining width but not get smaller than 15em */
+ grid-template-columns: minmax(min-content, max-content) minmax(15em, 1fr);
+ text-align: right; /* Right align for labels */
+ grid-row-gap: 0.5em;
+ grid-column-gap: 1em;
+
+ @media (max-width: 550px) {
+ grid-template-columns: 1fr;
+ text-align: left;
+ }
+
+ label {
+ text-transform: uppercase;
+ margin-top: 0.4em;
+ margin-bottom: -0.2em; // Only really applies when form is 1 column
+ }
+
+ }
+ }
+
+ .required-fields-notice {
+ margin-top: 1em;
+ text-align: right;
+
+ @media @breakpoint_table {
+ position: initial;
+ transform: none;
+ flex-basis: 100%;
+ }
+ }
+
+ .back-to-cart, .close-cart {
+ background-color: transparent !important;
+ border: 2px solid currentColor;
+ color: @menu-background !important;
+ display: inline-flex;
+ font-weight: bold;
+ align-items: center;
+ margin: 0;
+ justify-content: center;
+
+ @media @breakpoint_table {
+ order: 3; // Switch position (since we're in flex-direction: row-reverse)
+ }
+ }
+
+ .server-response {
+ max-width: 50ch;
+ margin: 2em auto 0;
+ }
+
+}