commercetools-8.x-1.2-alpha1/modules/commercetools_decoupled/js/organisms/CartForm.js
modules/commercetools_decoupled/js/organisms/CartForm.js
class CartForm extends HTMLElement {
constructor() {
super();
this.actionHandler = this.actionHandler.bind(this);
this.maxItems = window.drupalSettings.commercetoolsDecoupled.maxItems;
}
connectedCallback() {
this.renderForm();
}
async actionHandler(action, data) {
window.clearTimeout(this.delay);
this.delay = window.setTimeout(async () => {
this.isLoading = true;
this.renderForm();
const actions = [];
let successMessage;
switch (action) {
case 'plus-quantity':
case 'minus-quantity':
actions.push({
changeLineItemQuantity: {
lineItemId: data.lineItem.id,
quantity: data.quantity,
},
});
break;
case 'add-discount':
actions.push({ addDiscountCode: { code: data.code } });
successMessage = () => {
new Drupal.Message().add(
Drupal.t("Successfully redeemed code: '@code'", {
'@code': data.code,
}),
);
};
break;
case 'remove-discount':
actions.push({
removeDiscountCode: {
discountCode: { typeId: 'discount-code', id: data.id },
},
});
successMessage = () => {
new Drupal.Message().add(
Drupal.t(
"The following code has been removed from the cart: '@code'",
{ '@code': data.code },
),
);
};
break;
default:
actions.push({ removeLineItem: { lineItemId: data.lineItem.id } });
break;
}
try {
const result = await window.commercetools.updateCart({
id: '[current_cart:id]',
version: '[current_cart:version]',
actions,
allowErrors: true,
});
if (result?.errors) {
if (action === 'add-discount') {
CartForm.handleAddDiscountErrors(result.errors, data.code);
} else {
result.errors.forEach((error) => {
new Drupal.Message().add(error, { type: 'warning' });
});
}
} else {
this.cart = result;
if (typeof successMessage === 'function') {
successMessage();
} else {
new Drupal.Message().add(successMessage);
}
}
} catch (e) {
// If failed, show current cart.
}
document.dispatchEvent(
new CustomEvent('commercetoolsDecoupledCartUpdated', {
detail: {
totalItems: this.cart.lineItems.length,
},
}),
);
this.isLoading = false;
this.renderForm();
}, 1000);
}
generateSummaryContent() {
const checkoutPath =
window.drupalSettings.commercetoolsDecoupled?.checkoutPath;
if (!this.isSummaryCart) {
const discountBlock = this.isLoading ? '' : this.discountPart();
return `
<div class="col-lg-3 bg-body-tertiary">
<div class="p-5">
<h3 class="fw-bold mb-5">${Drupal.t('Summary')}</h3>
${discountBlock}
<hr class="my-4">
<div class="d-flex justify-content-between mb-5">
<p class='h6 fw-bold'>${Drupal.t('Total price')}</p>
<p class='h6 fw-bold'>${this.isLoading ? CartForm.placeholder(this.isLoading) : this.cart?.totalPrice?.localizedPrice || ''}</p>
</div>
${
this.isLoading
? ''
: `
<div class="form-actions form-wrapper mb-3">
<a href="${!checkoutPath ? '#' : Drupal.url(checkoutPath.substring(1))}" class="btn btn-primary ${!checkoutPath ? 'disabled' : ''}">${Drupal.t('Checkout')}</a>
</div>
`
}
</div>
</div>`;
}
return '';
}
renderForm() {
const summaryContent = this.generateSummaryContent();
this.innerHTML = `
<form class="commercetools-content-cart-form">
<section class="pb-4">
<div class="overflow-hidden">
<section class="w-100">
<div class="row">
<div class="col-12">
<div class="card card-registration card-registration-2 overflow-hidden border-0">
<div class="card-body p-0">
<div class="row g-0">
<div class="${this.isSummaryCart ? 'col-lg-12' : 'col-lg-9'}">
<div class="${this.isSummaryCart ? 'pt-2' : 'pt-5 pe-5'}">
${
!this.isSummaryCart
? `<div class="d-flex justify-content-between align-items-center mb-5">
<h3 class="fw-bold mb-0">${Drupal.t('Cart items')}</h3>
<h4 class="mb-0 text-muted fs-6">${this.isLoading ? CartForm.placeholder(this.isLoading) : Drupal.formatPlural(this.cart?.totalLineItemQuantity || 0, '1 item', '@count items')}</h4>
</div>`
: ''
}
${
!this.isLoading &&
(!this.cart?.lineItems ||
this.cart?.lineItems?.length === 0)
? `<div class="cart-empty-text">${Drupal.t('Your cart is empty.')}</div>`
: `<div class="cart-line-items"></div>`
}
</div>
</div>
${summaryContent}
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</section>
</form>
`;
if (!!this.cart?.lineItems?.length || this.isLoading) {
const LineItems = document.createElement('ct-line-items');
LineItems.lineStyle = this.isSummaryCart ? 'summary' : 'form';
LineItems.actionHandler = this.actionHandler;
LineItems.isLoading = this.isLoading;
const visibleItems = this.isSummaryCart
? this.cart.lineItems.slice(0, this.maxItems)
: this.cart.lineItems;
LineItems.lineItems = visibleItems;
this.querySelector('.cart-line-items').append(LineItems);
}
if (!this.isLoading && !this.isSummaryCart) {
this.querySelector('.discounts-inputs').appendChild(
this.createDiscountInputToCart(),
);
this.querySelector('.discounts-codes').appendChild(
this.createDiscountCodesButtons(),
);
}
}
discountPart() {
let discountPart = `<div class="discounts-inputs"></div><div class="discounts-codes"></div>`;
if (this.cart.isDiscounted) {
discountPart += `
<div class="d-flex justify-content-between">
<h6>${Drupal.t('Subtotal')}</h6>
<h6>${this.cart.originalTotalPrice.localizedPrice}</h6>
</div>
${
this.cart.discount?.centAmount > 0
? `
<div class="d-flex justify-content-between text-success">
<h6>${Drupal.t('Discount')}</h6>
<h6>- ${this.cart.discount.localizedPrice}</h6>
</div>
`
: ''
}
${(this.cart.discountCodes || [])
.map(
(dc) => `
<div class="d-flex justify-content-between text-success">
<h6>${dc.code}</h6>
<h6>- ${dc.amount.localizedPrice}</h6>
</div>
`,
)
.join('')}
`;
}
return discountPart;
}
createDiscountInputToCart() {
const template = document.createElement('template');
template.innerHTML = `
<div class="js-form-wrapper form-wrapper mb-3">
<div class="js-form-item js-form-type-textfield form-type-textfield mb-3">
<label for="edit-discount-code">${Drupal.t('Your discount code (optional)')}</label>
<input type="text" id="edit-discount-code" name="discount_code" class="form-control"/>
</div>
<div class="form-actions mb-3">
<button type="button" class="btn btn-primary disabled" data-cart-action="add-discount">
${Drupal.t('Redeem Code')}
</button>
</div>
</div>
`.trim();
const formItem = template.content.firstElementChild;
const input = formItem.querySelector('input');
const button = formItem.querySelector('button');
input.addEventListener('input', () => {
const isEmpty = input.value.trim() === '';
button.classList.toggle('disabled', isEmpty);
button.disabled = isEmpty;
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
button.click();
}
});
const onMultiple = (target, events, handler) =>
events.forEach((evt) => target.addEventListener(evt, handler));
onMultiple(button, ['click', 'pointerup', 'keydown'], (e) => {
if (input.value.trim() === '') {
return;
}
const action = button.dataset.cartAction;
const data = { code: input.value.trim() };
this.actionHandler(action, data);
});
return formItem;
}
createDiscountCodesButtons() {
const codes = this.cart.discountCodes;
if (!Array.isArray(codes) || codes.length === 0) {
return document.createElement('span');
}
const template = document.createElement('template');
template.innerHTML = `
<div class="d-flex flex-nowrap gap-2 mb-3">
${codes
.map(
({ id, code, name }) => `
<div class="bg-success p-1 d-flex align-items-center">
<span>${name}</span>
<button
type="button"
class="btn-close ms-2"
aria-label="Remove"
data-cart-action="remove-discount"
data-id="${id}"
data-key="${code}"
></button>
</div>
`,
)
.join('')}
</div>
`.trim();
const wrap = template.content.firstElementChild;
wrap.querySelectorAll('button[data-cart-action]').forEach((btn) => {
btn.addEventListener('pointerup', (e) => {
const { cartAction: action, id, key } = e.currentTarget.dataset;
this.actionHandler(action, { id, key });
});
});
return wrap;
}
static handleAddDiscountErrors(errors, code) {
const invalidMsg = errors.find((msg) =>
msg.toString().startsWith("The discount code '"),
);
if (invalidMsg) {
new Drupal.Message().add(
Drupal.t("The discount code '@code' was not found.", {
'@code': code,
}),
{ type: 'warning' },
);
} else {
window.commercetools.throwCommercetoolsException();
}
}
static placeholder() {
return `<span class="placeholderify"><span style="color: initial;">${Drupal.t('Loading...')}</span></span>`;
}
}
customElements.define('ct-cart-form', CartForm);
