From 185d1b3957edf7a79c5b87c5c5c752abe73715e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Ste=CC=A8pien=CC=81?= Date: Sun, 29 Oct 2023 02:41:24 +0200 Subject: [PATCH 01/11] Email subscription jquery removed --- _dev/js/utils/http/useHttpRequest.js | 5 +++ .../ps_emailsubscription/views/js/index.php | 34 ++++++++++++++++++ .../views/js/ps_emailsubscription.js | 36 +++++++++++++++++++ .../hook/ps_emailsubscription-column.tpl | 5 ++- .../templates/hook/ps_emailsubscription.tpl | 5 ++- 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 modules/ps_emailsubscription/views/js/index.php create mode 100644 modules/ps_emailsubscription/views/js/ps_emailsubscription.js diff --git a/_dev/js/utils/http/useHttpRequest.js b/_dev/js/utils/http/useHttpRequest.js index 289d36ec..b8dcb647 100644 --- a/_dev/js/utils/http/useHttpRequest.js +++ b/_dev/js/utils/http/useHttpRequest.js @@ -22,6 +22,11 @@ const useHttpRequest = (url, options = {}, addons = []) => { options.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; } + // Set default accept header + if (!(options.headers?.['accept'])) { + options.headers['accept'] = 'application/json, text/javascript, */*;'; + } + // Set default X-Requested-With header if (!(options.headers?.['X-Requested-With'])) { options.headers['X-Requested-With'] = 'XMLHttpRequest'; diff --git a/modules/ps_emailsubscription/views/js/index.php b/modules/ps_emailsubscription/views/js/index.php new file mode 100644 index 00000000..b7d9ae03 --- /dev/null +++ b/modules/ps_emailsubscription/views/js/index.php @@ -0,0 +1,34 @@ + + * @copyright 2007-2020 PrestaShop SA + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) + * International Registered Trademark & Property of PrestaShop SA + */ +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/modules/ps_emailsubscription/views/js/ps_emailsubscription.js b/modules/ps_emailsubscription/views/js/ps_emailsubscription.js new file mode 100644 index 00000000..5ef7fd04 --- /dev/null +++ b/modules/ps_emailsubscription/views/js/ps_emailsubscription.js @@ -0,0 +1,36 @@ + +DOMReady(() => { + const ALERT_BLOCK_CLASS = 'js-newsletter-form-alert'; + const buildAlertBlock = (msg, type) => parseToHtml(`
${msg}
`); + const handleSubmit = (e) => { + e.preventDefault(); + + // psemailsubscription_subscription - is a global variable + if (typeof psemailsubscription_subscription === 'undefined') { + return true; + } + + const alertBlock = document.querySelector(`.${ALERT_BLOCK_CLASS}`); + const form = e.target; + const data = fromSerialize(form); + + const { request } = useHttpRequest(psemailsubscription_subscription); + + request + .body(data) + .post() + .json((resp) => { + alertBlock?.remove(); + + if (resp.nw_error) { + form.prepend(buildAlertBlock(resp.msg, 'danger')); + } else { + form.prepend(buildAlertBlock(resp.msg, 'success')); + } + }); + } + + each('.js-newsletter-form', (el) => { + eventHandlerOn(el, 'submit', handleSubmit); + }) +}) diff --git a/modules/ps_emailsubscription/views/templates/hook/ps_emailsubscription-column.tpl b/modules/ps_emailsubscription/views/templates/hook/ps_emailsubscription-column.tpl index e39ae98e..a6f6c936 100644 --- a/modules/ps_emailsubscription/views/templates/hook/ps_emailsubscription-column.tpl +++ b/modules/ps_emailsubscription/views/templates/hook/ps_emailsubscription-column.tpl @@ -24,7 +24,10 @@ *}
-
+

{l s='Get our latest news and special sales' d='Shop.Theme.Global'}

diff --git a/modules/ps_emailsubscription/views/templates/hook/ps_emailsubscription.tpl b/modules/ps_emailsubscription/views/templates/hook/ps_emailsubscription.tpl index fa144240..3eb89b1c 100644 --- a/modules/ps_emailsubscription/views/templates/hook/ps_emailsubscription.tpl +++ b/modules/ps_emailsubscription/views/templates/hook/ps_emailsubscription.tpl @@ -27,7 +27,10 @@

{l s='Get our latest news and special sales' d='Shop.Theme.Global'}

- +
From 8dc66e2d758fe57061c7f3412c3584326826b818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Ste=CC=A8pien=CC=81?= Date: Sun, 29 Oct 2023 02:49:13 +0200 Subject: [PATCH 02/11] linting fix --- _dev/js/utils/http/useHttpRequest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_dev/js/utils/http/useHttpRequest.js b/_dev/js/utils/http/useHttpRequest.js index b8dcb647..f7122239 100644 --- a/_dev/js/utils/http/useHttpRequest.js +++ b/_dev/js/utils/http/useHttpRequest.js @@ -23,8 +23,8 @@ const useHttpRequest = (url, options = {}, addons = []) => { } // Set default accept header - if (!(options.headers?.['accept'])) { - options.headers['accept'] = 'application/json, text/javascript, */*;'; + if (!(options.headers?.accept)) { + options.headers.accept = 'application/json, text/javascript, */*;'; } // Set default X-Requested-With header From 467658fcebc073b1dd2fb0ee2c0a21d87057ac0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Ste=CC=A8pien=CC=81?= Date: Mon, 30 Oct 2023 23:35:48 +0100 Subject: [PATCH 03/11] Mailalerts jQuery removed --- modules/ps_emailalerts/js/mailalerts.js | 212 ++++++++++-------- .../front/mailalerts-account-line.tpl | 13 +- .../views/templates/hook/product-modal.tpl | 6 +- 3 files changed, 124 insertions(+), 107 deletions(-) diff --git a/modules/ps_emailalerts/js/mailalerts.js b/modules/ps_emailalerts/js/mailalerts.js index ba003bfd..3a435b32 100644 --- a/modules/ps_emailalerts/js/mailalerts.js +++ b/modules/ps_emailalerts/js/mailalerts.js @@ -1,124 +1,138 @@ -/** - * 2007-2020 PrestaShop. - * - * NOTICE OF LICENSE - * - * This source file is subject to the Academic Free License 3.0 (AFL-3.0) - * that is bundled with this package in the file LICENSE.txt. - * It is also available through the world-wide-web at this URL: - * https://opensource.org/licenses/AFL-3.0 - * If you did not receive a copy of the license and are unable to - * obtain it through the world-wide-web, please send an email - * to license@prestashop.com so we can send you a copy immediately. - * - * DISCLAIMER - * - * Do not edit or add to this file if you wish to upgrade PrestaShop to newer - * versions in the future. If you wish to customize PrestaShop for your - * needs please refer to http://www.prestashop.com for more information. - * - * @author PrestaShop SA - * @copyright 2007-2020 PrestaShop SA - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) - * International Registered Trademark & Property of PrestaShop SA - */ - -$(document).ready(function() { - - $('#email-alert-modal').on('hidden.bs.modal', function() { - resetForm(); - clearAlert(); - }) - $(document).on('click', '.js-mailalert-submit', function(e) { - e.preventDefault(); - addNotification(e); - }); +DOMReady(() => { + const DOM_SELECTORS = { + ALERT_BLOCK: '.js-mailalert-alert-box', + FORM: '.js-mailalert', + SUBMIT_BTN: '.js-mailalert-submit', + EMAIL_INPUT: '.js-mailalert-email', + MODAL: '#email-alert-modal', + MODAL_BTN: '.js-mailalert-modal-btn', + REMOVE_BTN: '.js-remove-email-alert', + INPUT_ID_PRODUCT: '.js-mailalert-id-product', + INPUT_ID_PRODUCT_ATTRIBUTE: '.js-mailalert-id-product-attribute', + PRODUCT_MINIATURE: '.js-mailalert-product-miniature', + }; + + const clearAlert = () => { + each(DOM_SELECTORS.ALERT_BLOCK, (el) => el.innerHTML = ''); + } - $('.js-remove-email-alert').on('click', function(e){ - e.preventDefault(); + const resetForm = () => { + each(DOM_SELECTORS.EMAIL_INPUT, (el) => el.value = ''); + each(`${DOM_SELECTORS.FORM} [name=psgdpr_consent_checkbox]`, (el) => el.checked = false); + } - var $self = $(this); - var ids = $self.attr('rel').replace('js-id-emailalerts-', ''); - ids = ids.split('-'); - var id_product_mail_alert = ids[0]; - var id_product_attribute_mail_alert = ids[1]; - var $parent = $self.closest('.js-mailalert-product-miniature'); - - $.ajax({ - url: $self.data('url'), - type: "POST", - data: { - 'id_product': id_product_mail_alert, - 'id_product_attribute': id_product_attribute_mail_alert - }, - success: function(result) - { - if (result == '0') - { - $parent.fadeOut("normal", function() - { - $parent.remove(); - }); - } - } - }); - }); + const setAlert = (message, type) => { + const alertBox = document.querySelector(DOM_SELECTORS.ALERT_BLOCK); + const alert = parseToHtml(`
${message}
`); - function resetForm() { - $('.js-mailalert-email').val(''); - $('.js-mailalert [name=psgdpr_consent_checkbox]').prop('checked', false); - } + if (type === 'success') { + alert.classList.add('alert-success'); + } else if (type === 'danger') { + alert.classList.add('alert-danger'); + } - function addNotification(e) { - var $idInputs = $('.js-mailalert-id-input'); - var $emailInput = $('.js-mailalert-email'); - var $btn = $(e.currentTarget); - var $modal = $('#email-alert-modal'); + alert.innerText = message; - $btn.attr('disabled', true); + alertBox?.append(alert); + } + + const handleAddNotification = (email, idProduct, idProductAttribute) => { + const submitBtn = document.querySelector(DOM_SELECTORS.SUBMIT_BTN); + const form = document.querySelector(DOM_SELECTORS.FORM); + const url = form?.dataset.url; + submitBtn.setAttribute('disabled', true); clearAlert(); - $.ajax({ - type: 'POST', - url: $('.js-mailalert').data('url'), - data: 'id_product=' + $idInputs[0].value + '&id_product_attribute=' + $idInputs[1].value + '&customer_email=' + $emailInput.val(), - success: function (resp) { - resp = JSON.parse(resp); - var alertType = resp.error ? 'danger' : 'success'; - setAlert(resp.message, alertType); + if (!url) { + return; + } + const { request } = useHttpRequest(url); + + request + .query({ + id_product: idProduct, + id_product_attribute: idProductAttribute, + customer_email: email, + }) + .post() + .json((resp) => { + const alertType = resp.error ? 'danger' : 'success'; + + setAlert(resp.message, alertType); if (resp.error) { - $btn.removeAttr('disabled'); + submitBtn.removeAttribute('disabled'); } else { - setTimeout(function() { - $modal.modal('hide'); - $('.js-mailalert-modal-btn').hide(); + const modal = document.querySelector(DOM_SELECTORS.MODAL); + + eventHandlerOff(modal, 'hidden.bs.modal', handleModalClose); + eventHandlerOn(modal, 'hidden.bs.modal', handleModalClose); + + setTimeout(() => { + const modalInstance = bootstrap.Modal.getInstance(modal); + const modalBtn = document.querySelector(DOM_SELECTORS.MODAL_BTN); + + modalInstance?.hide(); + modalBtn?.classList.add('d-none'); }, 2500); } - } - }); + }); } - function setAlert(message, type) { - var $alertBox = $('.js-mailalert-alert-box'); - var $alert = $('
').addClass('alert'); + const handleSubmitClick = (e) => { + e.preventDefault(); - if (type == 'success') { - $alert.addClass('alert-success'); - } else if (type == 'danger') { - $alert.addClass('alert-danger'); - } + const inputIdProduct = document.querySelector(DOM_SELECTORS.INPUT_ID_PRODUCT); + const inputIdProductAttribute = document.querySelector(DOM_SELECTORS.INPUT_ID_PRODUCT_ATTRIBUTE); + const emailInput = document.querySelector(DOM_SELECTORS.EMAIL_INPUT); + const idProduct = inputIdProduct?.value; + const idProductAttribute = inputIdProductAttribute?.value || 0; + const email = emailInput?.value; + + handleAddNotification(email, idProduct, idProductAttribute); + } + + const handelRemoveNotification = (url, idProduct, idProductAttribute, elementToRemove) => { + const { request } = useHttpRequest(url); + + request + .query({ + id_product: idProduct, + id_product_attribute: idProductAttribute, + }) + .post() + .json((resp) => { + if (resp != '0') { + return; + } - $alert.text(message); + if (elementToRemove) { + elementToRemove?.remove(); + } + }) + } + + const handleRemoveClick = (e) => { + e.preventDefault(); + const btn = e.delegateTarget; - $alertBox.html($alert) + const idProduct = btn.dataset.idProduct; + const idProductAttribute = btn.dataset.idProductAttribute; + const url = btn.dataset.url; + const elementToRemove = btn.closest(DOM_SELECTORS.PRODUCT_MINIATURE); + + handelRemoveNotification(url, idProduct, idProductAttribute, elementToRemove); } - function clearAlert() { - $('.js-mailalert-alert-box').html(''); + const handleModalClose = (e) => { + resetForm(); + clearAlert(); } + + eventHandlerOn(document, 'click', DOM_SELECTORS.SUBMIT_BTN, handleSubmitClick); + eventHandlerOn(document, 'click', DOM_SELECTORS.REMOVE_BTN, handleRemoveClick); }); diff --git a/modules/ps_emailalerts/views/templates/front/mailalerts-account-line.tpl b/modules/ps_emailalerts/views/templates/front/mailalerts-account-line.tpl index c142763c..90e24051 100644 --- a/modules/ps_emailalerts/views/templates/front/mailalerts-account-line.tpl +++ b/modules/ps_emailalerts/views/templates/front/mailalerts-account-line.tpl @@ -37,11 +37,14 @@ {$mailAlert.attributes_small}
diff --git a/modules/ps_emailalerts/views/templates/hook/product-modal.tpl b/modules/ps_emailalerts/views/templates/hook/product-modal.tpl index 03cc6188..8114e9a0 100644 --- a/modules/ps_emailalerts/views/templates/hook/product-modal.tpl +++ b/modules/ps_emailalerts/views/templates/hook/product-modal.tpl @@ -11,7 +11,7 @@ {/block} {block name='modal_close'} {/block}
@@ -40,8 +40,8 @@ {if isset($id_module)} {hook h='displayGDPRConsent' id_module=$id_module} {/if} - - + +
{/block} {block name='modal_footer'} From 6cb2b5b838769a8dbeab6b9ba187965a9f8d7d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Ste=CC=A8pien=CC=81?= Date: Tue, 31 Oct 2023 07:33:56 +0100 Subject: [PATCH 04/11] productcomments - post-comment.js rewritten --- .../productcomments/views/js/post-comment.js | 208 ++++++++++-------- .../views/templates/hook/alert-modal.tpl | 5 +- .../templates/hook/post-comment-modal.tpl | 15 +- 3 files changed, 125 insertions(+), 103 deletions(-) diff --git a/modules/productcomments/views/js/post-comment.js b/modules/productcomments/views/js/post-comment.js index 02398c20..c81bee28 100644 --- a/modules/productcomments/views/js/post-comment.js +++ b/modules/productcomments/views/js/post-comment.js @@ -1,129 +1,143 @@ -/** - * Copyright since 2007 PrestaShop SA and Contributors - * PrestaShop is an International Registered Trademark & Property of PrestaShop SA - * - * NOTICE OF LICENSE - * - * This source file is subject to the Academic Free License 3.0 (AFL-3.0) - * that is bundled with this package in the file LICENSE.md. - * It is also available through the world-wide-web at this URL: - * https://opensource.org/licenses/AFL-3.0 - * If you did not receive a copy of the license and are unable to - * obtain it through the world-wide-web, please send an email - * to license@prestashop.com so we can send you a copy immediately. - * - * DISCLAIMER - * - * Do not edit or add to this file if you wish to upgrade PrestaShop to newer - * versions in the future. If you wish to customize PrestaShop for your - * needs please refer to https://devdocs.prestashop.com/ for more information. - * - * @author PrestaShop SA and Contributors - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) - */ - -jQuery(document).ready(function () { - const $ = jQuery; - $('body').on('click', '.js-post-product-comment', function (event) { - event.preventDefault(); - showPostCommentModal(); - }); +DOMReady(() => { + const DOM_SELECTORS = { + BTN_POST_MODAL_BTN: '.js-post-product-comment', + POST_COMMENT_MODAL: '#post-product-comment-modal', + COMMENT_POSTED_MODAL: '#product-comment-posted-modal', + COMMENT_POST_ERROR_MODAL: '#product-comment-post-error', + COMMENT_POST_ERROR_MESSAGE: '#product-comment-post-error-message', + POST_COMMENT_FORM: '#post-product-comment-form', + POST_COMMENT_FORM_INPUTS: '#post-product-comment-form input[type="text"]', + POST_COMMENT_FORM_TEXTAREA: '#post-product-comment-form textarea', + POST_COMMENT_FORM_CRITERION_RATING: '#post-product-comment-form .criterion-rating input', + }; + const postCommentModal = document.querySelector(DOM_SELECTORS.POST_COMMENT_MODAL); + const commentPostedModal = document.querySelector(DOM_SELECTORS.COMMENT_POSTED_MODAL); + const commentPostErrorModal = document.querySelector(DOM_SELECTORS.COMMENT_POST_ERROR_MODAL); + const commentForm = document.querySelector(DOM_SELECTORS.POST_COMMENT_FORM); + const getCommentPostModalInstance = () => bootstrap.Modal.getOrCreateInstance(postCommentModal); + const getCommentPostedModalInstance = () => bootstrap.Modal.getOrCreateInstance(commentPostedModal); + const getCommentPostErrorModalInstance = () => bootstrap.Modal.getOrCreateInstance(commentPostErrorModal); + + const clearPostCommentForm = () => { + each(`${DOM_SELECTORS.POST_COMMENT_FORM_INPUTS}, ${DOM_SELECTORS.POST_COMMENT_FORM_TEXTAREA}`, (formElement) => { + formElement.value = ''; + formElement.classList.remove('is-invalid'); + }); + each(DOM_SELECTORS.POST_COMMENT_FORM_CRITERION_RATING, (formElement) => { + formElement.value = 5; - const postCommentModal = $('#post-product-comment-modal'); - postCommentModal.on('hidden.bs.modal', function () { - postCommentModal.modal('hide'); + const event = new Event('change'); + formElement.dispatchEvent(event); + }); + } + + const showPostErrorModal = (errorMessage) => { + getCommentPostedModalInstance()?.hide(); + getCommentPostModalInstance()?.hide(); clearPostCommentForm(); - }); - const commentPostedModal = $('#product-comment-posted-modal'); - const commentPostErrorModal = $('#product-comment-post-error'); + const errorMessageElement = document.querySelector(DOM_SELECTORS.COMMENT_POST_ERROR_MESSAGE); + + if (errorMessageElement) { + errorMessageElement.innerHTML = errorMessage; + } - function showPostCommentModal() { - commentPostedModal.modal('hide'); - commentPostErrorModal.modal('hide'); - postCommentModal.modal('show'); + getCommentPostErrorModalInstance()?.show(); } - function showCommentPostedModal() { - postCommentModal.modal('hide'); - commentPostErrorModal.modal('hide'); + const showCommentPostedModal = () => { + getCommentPostErrorModalInstance()?.hide(); + getCommentPostModalInstance()?.hide(); + getCommentPostedModalInstance()?.show(); + clearPostCommentForm(); - commentPostedModal.modal('show'); } - function showPostErrorModal(errorMessage) { - postCommentModal.modal('hide'); - commentPostedModal.modal('hide'); - clearPostCommentForm(); - $('#product-comment-post-error-message').html(errorMessage); - commentPostErrorModal.modal('show'); + const showPostCommentModal = () => { + getCommentPostErrorModalInstance()?.hide(); + getCommentPostedModalInstance()?.hide(); + getCommentPostModalInstance()?.show(); + } + + const handleClickPostModalBtn = (e) => { + e.preventDefault(); + showPostCommentModal(); } - function clearPostCommentForm() { - $('#post-product-comment-form input[type="text"]').val(''); - $('#post-product-comment-form input[type="text"]').removeClass('vis-invalid'); - $('#post-product-comment-form textarea').val(''); - $('#post-product-comment-form textarea').removeClass('is-invalid'); - $('#post-product-comment-form .criterion-rating input').val(3).change(); + const handlePostCommentModalHidden = () => { + clearPostCommentForm(); } - function initCommentModal() { - $('#post-product-comment-modal .grade-stars').rating(); - $('body').on('click', '.js-post-product-comment', function (event) { - event.preventDefault(); - showPostCommentModal(); + const handlePostCommentModal = (url, formData) => { + const { request } = useHttpRequest(url, { + headers: { + accept: '*/*', + } }); - $('#post-product-comment-form').submit(submitCommentForm); + request + .post(formData) + .json((jsonData) => { + if (jsonData) { + if (jsonData.success) { + clearPostCommentForm(); + showCommentPostedModal(); + } else if (jsonData.errors || jsonData.error) { + if (jsonData.errors) { + const { errors } = jsonData; + const errorList = ` +
    + ${errors.map((error) => `
  • ${error}
  • `).join('')} +
+ `; + + showPostErrorModal(errorList); + } else if (jsonData.error) { + showPostErrorModal(jsonData.error); + } + } + } else { + showPostErrorModal(`

${productCommentPostErrorMessage}

`); + } + }) + .catch(() => { + showPostErrorModal(`

${productCommentPostErrorMessage}

`); + }); } - function submitCommentForm(event) { + const handleSubmitCommentForm = (event) => { event.preventDefault(); - var formData = $(this).serializeArray(); - if (!validateFormData(formData)) { + const form = event.target; + const formDataArray = formSerializeArray(form); + const formData = fromSerialize(form); + const url = form.action; + + if (!validateFormData(formDataArray)) { return; } - $.post($(this).attr('action'), $(this).serialize(), function(jsonData) { - if (jsonData) { - if (jsonData.success) { - clearPostCommentForm(); - showCommentPostedModal(); - } else { - if (jsonData.errors) { - var errorList = '
    '; - for (var i = 0; i < jsonData.errors.length; ++i) { - errorList += '
  • ' + jsonData.errors[i] + '
  • '; - } - errorList += '
'; - showPostErrorModal(errorList); - } else { - const decodedErrorMessage = $("
").html(jsonData.error).text(); - showPostErrorModal(decodedErrorMessage); - } - } - } else { - showPostErrorModal(productCommentPostErrorMessage); - } - }).fail(function() { - showPostErrorModal(productCommentPostErrorMessage); - }); + + handlePostCommentModal(url, formData); } - function validateFormData(formData) { - var isValid = true; - formData.forEach(function(formField) { - const fieldSelector = '#post-product-comment-form [name="'+formField.name+'"]'; + const validateFormData = (formData) => { + let isValid = true; + + formData.forEach((formField) => { + const fieldSelector = '[name="'+formField.name+'"]'; + const fieldElement = commentForm.querySelector(fieldSelector); + if (!formField.value) { - $(fieldSelector).addClass('is-invalid'); + fieldElement.classList.add('is-invalid'); isValid = false; } else { - $(fieldSelector).removeClass('is-invalid'); + fieldElement.classList.remove('is-invalid'); } }); return isValid; } - initCommentModal(); + eventHandlerOn(document, 'click', DOM_SELECTORS.BTN_POST_MODAL_BTN, handleClickPostModalBtn); + eventHandlerOn(postCommentModal, 'hidden.bs.modal', handlePostCommentModalHidden); + eventHandlerOn(commentForm, 'submit', handleSubmitCommentForm); }); diff --git a/modules/productcomments/views/templates/hook/alert-modal.tpl b/modules/productcomments/views/templates/hook/alert-modal.tpl index d9cc1e34..3f9a3cb5 100644 --- a/modules/productcomments/views/templates/hook/alert-modal.tpl +++ b/modules/productcomments/views/templates/hook/alert-modal.tpl @@ -49,7 +49,10 @@
diff --git a/modules/productcomments/views/templates/hook/post-comment-modal.tpl b/modules/productcomments/views/templates/hook/post-comment-modal.tpl index 8dfc383e..4eb587a7 100644 --- a/modules/productcomments/views/templates/hook/post-comment-modal.tpl +++ b/modules/productcomments/views/templates/hook/post-comment-modal.tpl @@ -33,7 +33,7 @@ @@ -44,10 +44,15 @@
  • -
    +
    + {for $i=1 to 5} + + {/for}
  • From fc13dfeb98cbde49829bcc3c2b22537cb173adac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Ste=CC=A8pien=CC=81?= Date: Thu, 2 Nov 2023 03:00:43 +0100 Subject: [PATCH 05/11] product comments module post and list refactoring --- .../product/_comments-pagination.scss | 15 - _dev/css/theme/components/product/_index.scss | 1 - _dev/css/theme/components/product/_stars.scss | 20 +- _dev/js/theme/windowExpose.js | 5 + .../views/js/jquery.rating.plugin.js | 331 ++++++++-------- .../productcomments/views/js/list-comments.js | 369 ++++++++++++------ .../productcomments/views/js/post-comment.js | 28 +- .../views/js/productListingComments.js | 2 +- .../views/templates/hook/alert-modal.tpl | 9 - .../views/templates/hook/confirm-modal.tpl | 39 +- .../templates/hook/post-comment-modal.tpl | 21 +- 11 files changed, 503 insertions(+), 337 deletions(-) delete mode 100644 _dev/css/theme/components/product/_comments-pagination.scss diff --git a/_dev/css/theme/components/product/_comments-pagination.scss b/_dev/css/theme/components/product/_comments-pagination.scss deleted file mode 100644 index d0583e1b..00000000 --- a/_dev/css/theme/components/product/_comments-pagination.scss +++ /dev/null @@ -1,15 +0,0 @@ -.comments-pagination { - ul { - @extend .pagination; - margin: 0; - } - - li { - @extend .page-item; - } - - span { - @extend .page-link; - cursor: pointer; - } -} diff --git a/_dev/css/theme/components/product/_index.scss b/_dev/css/theme/components/product/_index.scss index 94a11657..fd0698e3 100644 --- a/_dev/css/theme/components/product/_index.scss +++ b/_dev/css/theme/components/product/_index.scss @@ -5,4 +5,3 @@ @import "stars"; @import "products-list"; @import "product-slider"; -@import "comments-pagination"; diff --git a/_dev/css/theme/components/product/_stars.scss b/_dev/css/theme/components/product/_stars.scss index a213a2b5..e5682739 100644 --- a/_dev/css/theme/components/product/_stars.scss +++ b/_dev/css/theme/components/product/_stars.scss @@ -5,17 +5,27 @@ top: 0; left: 0; display: flex; + flex-wrap: nowrap; + overflow: hidden; + + &.star-content-empty { + position: static; + } .star, .star-on, .star-hover { + flex: 0 0 auto; display: block; - flex: auto; width: 20px; height: 20px; margin-left: 3px; background: url("../img/icons/star_gray.svg") no-repeat 0 0 transparent; background-size: 20px; + + &:first-child { + margin-left: 0; + } } .star-on, @@ -35,6 +45,10 @@ height: 16px; margin-left: 2px; background-size: 16px; + + &:first-child { + margin-left: 0; + } } } } @@ -42,11 +56,9 @@ .grade-stars { position: relative; display: inline-block; - min-width: 120px; height: 20px; &.small-stars { - min-width: 70px; height: 16px; } } @@ -61,6 +73,6 @@ background: rgba($white, 0.4); .grade-stars { - display: block; + display: inline-block; } } diff --git a/_dev/js/theme/windowExpose.js b/_dev/js/theme/windowExpose.js index d03ae279..80f994e3 100644 --- a/_dev/js/theme/windowExpose.js +++ b/_dev/js/theme/windowExpose.js @@ -13,6 +13,8 @@ import { getAllSiblingsBeforeElement, getAllSiblingsAfterElement } from '../util import { fromSerializeObject, fromSerialize, formSerializeArray } from '../utils/form/formSerialize'; import useToggleDisplay from '../utils/display/useToggleDisplay'; +const { hide, show, toggle } = useToggleDisplay(); + exposeToWindow('eventHandlerOn', on); exposeToWindow('eventHandlerOne', one); exposeToWindow('eventHandlerOff', off); @@ -31,3 +33,6 @@ exposeToWindow('fromSerializeObject', fromSerializeObject); exposeToWindow('fromSerialize', fromSerialize); exposeToWindow('formSerializeArray', formSerializeArray); exposeToWindow('useToggleDisplay', useToggleDisplay); +exposeToWindow('hide', hide); +exposeToWindow('show', show); +exposeToWindow('toggle', toggle); diff --git a/modules/productcomments/views/js/jquery.rating.plugin.js b/modules/productcomments/views/js/jquery.rating.plugin.js index 8c2dd355..0cd3303c 100644 --- a/modules/productcomments/views/js/jquery.rating.plugin.js +++ b/modules/productcomments/views/js/jquery.rating.plugin.js @@ -1,164 +1,191 @@ -/** - * Copyright since 2007 PrestaShop SA and Contributors - * PrestaShop is an International Registered Trademark & Property of PrestaShop SA - * - * NOTICE OF LICENSE - * - * This source file is subject to the Academic Free License 3.0 (AFL-3.0) - * that is bundled with this package in the file LICENSE.md. - * It is also available through the world-wide-web at this URL: - * https://opensource.org/licenses/AFL-3.0 - * If you did not receive a copy of the license and are unable to - * obtain it through the world-wide-web, please send an email - * to license@prestashop.com so we can send you a copy immediately. - * - * DISCLAIMER - * - * Do not edit or add to this file if you wish to upgrade PrestaShop to newer - * versions in the future. If you wish to customize PrestaShop for your - * needs please refer to https://devdocs.prestashop.com/ for more information. - * - * @author PrestaShop SA and Contributors - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) - */ - -jQuery.fn.rating = function(generalOptions) { - const $ratings = $(this); - - $ratings.each(function initRating() { - const $ratingComponent = $(this); - var options = generalOptions ? generalOptions : {}; - const ratingAddedClass = 'js-rating-added'; - if (!options.grade && $ratingComponent.data('grade')) { - options.grade = $ratingComponent.data('grade'); - } - if (!options.min && $ratingComponent.data('min')) { - options.min = $ratingComponent.data('min'); + +const starRating = (element, grade = null, max = 5) => { + const STAR_CLASS = 'star'; + const STAR_ACTIVE_CLASS = 'star-on'; + const INITIALIZED_CLASS = 'grade-initialized'; + + if (!element) { + console.warn('No element provided'); + return; + } + + const tempGrade = grade || element.dataset.grade; + const ratingGrade = Number.parseFloat(tempGrade); + + if (!ratingGrade || Number.isNaN(ratingGrade)) { + console.warn('No grade provided or grade is not a number'); + return; + } + + + const getGradeParsed = (gradeToNormalize) => { + const returnValue = gradeToNormalize; + + if (returnValue > max) { + return max; } - if (!options.max && $ratingComponent.data('max')) { - options.max = $ratingComponent.data('max'); + + return returnValue; + }; + const calcPercentageWidth = (value) => { + const gradeParsed = getGradeParsed(value); + + return (gradeParsed / max) * 100; + }; + + const buildStarsTemplate = () => { + const ratingFull = document.createElement('div'); + ratingFull.classList.add('star-content', 'star-content-full'); + const ratingEmpty = document.createElement('div'); + ratingEmpty.classList.add('star-content', 'star-content-empty'); + + for (let i = 1; i <= max; i += 1) { + const star = document.createElement('span'); + star.classList.add(STAR_CLASS); + star.dataset.grade = i; + const starCopy = star.cloneNode(true); + + ratingEmpty.appendChild(star); + + starCopy.classList.add(STAR_ACTIVE_CLASS); + + ratingFull.appendChild(starCopy); } - if (!options.input && $ratingComponent.data('input')) { - options.input = $ratingComponent.data('input'); + + const gradeCalculated = getGradeParsed(ratingGrade); + const percentage = calcPercentageWidth(gradeCalculated); + + ratingFull.style.width = `${percentage}%`; + + element.appendChild(ratingEmpty); + element.appendChild(ratingFull); + }; + + const updateStars = () => { + const gradeCalculated = getGradeParsed(ratingGrade); + const percentage = calcPercentageWidth(gradeCalculated); + const ratingFull = element.querySelector('.star-content-full'); + ratingFull.style.width = `${percentage}%`; + }; + + if (element.classList.contains(INITIALIZED_CLASS)) { + updateStars(); + } else { + buildStarsTemplate(); + } + + element.classList.add(INITIALIZED_CLASS); +}; + +const starRatingInput = (element, inputName, grade = null, max = 5) => { + const tempGrade = grade || element.dataset.grade; + const ratingGrade = Number.parseFloat(tempGrade); + + if (!ratingGrade || Number.isNaN(ratingGrade)) { + console.warn('No grade provided or grade is not a number'); + return; + } + + let currentGrade = ratingGrade; + + const INPUT_INIT_CLASS = 'grade-input-initialized'; + const INPUT_CLASS = 'js-grade-input'; + + const handleInputChange = ({target}) => { + const targetGrade = target.value; + currentGrade = targetGrade; + + starRating(element, targetGrade, max); + }; + + const buildInput = (name, value) => { + const input = document.createElement('input'); + input.type = 'radio'; + input.name = name; + input.value = value; + input.style.display = 'none'; + input.classList.add(INPUT_CLASS); + + if (value === currentGrade) { + input.checked = true; } - var componentOptions = jQuery.extend({ - grade: null, - input: null, - min: 1, - max: 5, - starWidth: 20 - }, options); - - if ($ratingComponent.hasClass(ratingAddedClass)) { - return; + + return input; + }; + + const buildTemplate = () => { + for (let i = 1; i <= max; i += 1) { + const input = buildInput(inputName, i); + element.appendChild(input); } + }; - const minValue = Math.min(componentOptions.min, componentOptions.max); - const maxValue = Math.max(componentOptions.min, componentOptions.max); - const ratingValue = Math.min(Math.max(minValue, componentOptions.grade), maxValue); - - $ratingComponent.html(''); - $ratingComponent.append('
    '); - $ratingComponent.append('
    '); - - const emptyStars = $('.star-empty', this); - const fullStars = $('.star-full', this); - const emptyStar = $('
    '); - const fullStar = $('
    '); - - var ratingInput; - if (componentOptions.input) { - ratingInput = $(''); - ratingInput.val(ratingValue); - ratingInput.css('display', 'none'); - ratingInput.change(displayInteractiveGrade); - $ratingComponent.append(ratingInput); - initInteractiveGrade(); + const handleMouseHoverEvent = ({target, type}) => { + if (type === 'mouseout') { + starRating(element, currentGrade, max); } else { - displayGrade(ratingValue); + const starGrade = target.dataset.grade; + starRating(element, starGrade, max); } + }; + + const refreshInputsValue = (value) => { + const inputs = element.querySelectorAll(`.${INPUT_CLASS}`); - function initInteractiveGrade() { - emptyStars.html(''); - fullStars.html(''); - var newStar; - for (var i = minValue; i <= maxValue; ++i) { - newStar = emptyStar.clone(); - newStar.data('grade', i); - newStar.hover(function overStar() { - var overIndex = $('.star', fullStars).index($(this)); - $('.star', fullStars).each(function overStars() { - $(this).removeClass('star-on'); - var starIndex = $('.star', fullStars).index($(this)); - if (starIndex <= overIndex) { - $(this).addClass('star-hover'); - } else { - $(this).removeClass('star-hover'); - } - }); - }); - newStar.click(function selectGrade() { - var selectedGrade = $(this).data('grade'); - ratingInput.val(selectedGrade); - }); - fullStars.append(newStar); + each(inputs, (input) => { + if (Number.parseInt(input.value, 10) === Number.parseInt(value, 10)) { + input.checked = true; } + }); + } - fullStars.hover(function(){}, displayInteractiveGrade); - displayInteractiveGrade(); - } + const handleMouseClickEvent = ({target}) => { + const newGrade = target.dataset.grade; - function displayInteractiveGrade() { - $('.star', fullStars).each(function displayStar() { - var starValue = $(this).data('grade'); - $(this).removeClass('star-hover'); - if (starValue <= ratingInput.val()) { - $(this).addClass('star-on'); - } else { - $(this).removeClass('star-on'); - } - }); - } + currentGrade = newGrade; + refreshInputsValue(newGrade); + }; - function displayGrade(grade) { - emptyStars.html(''); - fullStars.html(''); - var newStar; - for (var i = minValue; i <= maxValue; ++i) { - if (i <= Math.floor(grade)) { - newStar = emptyStar.clone(); - newStar.css('visibility', 'hidden'); - emptyStars.append(newStar); - fullStars.append(fullStar.clone()); - } else if (i > Math.ceil(grade)) { - newStar = emptyStar.clone(); - emptyStars.append(newStar.clone()); - } else { - //This the partial star composed of - // - one invisible partial empty star - // - one visible partial empty star (remaining part) - // - one visible partial full star - var fullWidth = (grade - i + 1) * componentOptions.starWidth; - var emptyWidth = componentOptions.starWidth - fullWidth; - newStar = emptyStar.clone(); - newStar.css('visibility', 'hidden'); - newStar.css('width', fullWidth); - emptyStars.append(newStar); - - newStar = emptyStar.clone(); - newStar.css('width', emptyWidth); - newStar.css('background-position', '0px -'+fullWidth+'px'); - newStar.css('background-position', '-'+fullWidth+'px 0px'); - newStar.css('marginLeft', 0); - emptyStars.append(newStar); - - fullStar.css('width', fullWidth); - fullStars.append(fullStar.clone()); - } - - $ratingComponent.addClass(ratingAddedClass); - } - } - }); -} + const attachEvents = () => { + const stars = element.querySelectorAll('.star'); + const inputs = element.querySelectorAll(`.${INPUT_CLASS}`); + + each(stars, (star) => { + eventHandlerOff(star, 'click', handleMouseClickEvent); + eventHandlerOn(star, 'click', handleMouseClickEvent); + + eventHandlerOff(star, 'mouseover', handleMouseHoverEvent); + eventHandlerOn(star, 'mouseover', handleMouseHoverEvent); + + eventHandlerOff(star, 'mouseout', handleMouseHoverEvent); + eventHandlerOn(star, 'mouseout', handleMouseHoverEvent); + }); + + each(inputs, (input) => { + eventHandlerOff(input, 'change', handleInputChange); + eventHandlerOn(input, 'change', handleInputChange); + }); + }; + + const refresh = () => { + refreshInputsValue(currentGrade); + starRating(element, currentGrade, max); + }; + + const init = () => { + starRating(element, grade, max); + buildTemplate(); + attachEvents(); + + element.classList.add(INPUT_INIT_CLASS); + }; + + if (element.classList.contains(INPUT_INIT_CLASS)) { + refresh(); + } else { + init(); + } +}; + +window.starRating = starRating; +window.starRatingInput = starRatingInput; diff --git a/modules/productcomments/views/js/list-comments.js b/modules/productcomments/views/js/list-comments.js index 013c848d..cbd36e56 100644 --- a/modules/productcomments/views/js/list-comments.js +++ b/modules/productcomments/views/js/list-comments.js @@ -1,102 +1,214 @@ -/** - * Copyright since 2007 PrestaShop SA and Contributors - * PrestaShop is an International Registered Trademark & Property of PrestaShop SA - * - * NOTICE OF LICENSE - * - * This source file is subject to the Academic Free License 3.0 (AFL-3.0) - * that is bundled with this package in the file LICENSE.md. - * It is also available through the world-wide-web at this URL: - * https://opensource.org/licenses/AFL-3.0 - * If you did not receive a copy of the license and are unable to - * obtain it through the world-wide-web, please send an email - * to license@prestashop.com so we can send you a copy immediately. - * - * DISCLAIMER - * - * Do not edit or add to this file if you wish to upgrade PrestaShop to newer - * versions in the future. If you wish to customize PrestaShop for your - * needs please refer to https://devdocs.prestashop.com/ for more information. - * - * @author PrestaShop SA and Contributors - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) - */ - -jQuery(document).ready(function () { - const $ = jQuery; - const commentsList = $('#product-comments-list'); - const emptyProductComment = $('#empty-product-comment'); - const commentsListUrl = commentsList.data('list-comments-url'); - const updateCommentUsefulnessUrl = commentsList.data('update-comment-usefulness-url'); - const reportCommentUrl = commentsList.data('report-comment-url'); - const commentPrototype = commentsList.data('comment-item-prototype'); - - emptyProductComment.hide(); - $('.comments-note .grade-stars').rating(); + +DOMReady(() => { + const DOM_SELECTORS = { + PRODUCT_COMMENTS_LIST: '#product-comments-list', + EMPTY_PRODUCT_COMMENT: '#empty-product-comment', + PRODUCT_COMMENTS_LIST_PAGINATION: '#product-comments-list-pagination', + UPDATE_COMMENT_POST_ERROR_MODAL: '#update-comment-usefulness-post-error', + UPDATE_COMMENT_POST_ERROR_MESSAGE: '#update-comment-usefulness-post-error-message', + REPORT_COMMENT_POST_ERROR_MODAL: '#report-comment-post-error', + REPORT_COMMENT_POST_ERROR_MESSAGE: '#report-comment-post-error-message', + REPORT_COMMENT_POSTED_MODAL: '#report-comment-posted', + REPORT_COMMENT_CONFIRMATION_MODAL: '#report-comment-confirmation', + + }; + const commentsList = document.querySelector(DOM_SELECTORS.PRODUCT_COMMENTS_LIST); + const emptyProductComment = document.querySelector(DOM_SELECTORS.EMPTY_PRODUCT_COMMENT); + const updateCommentPostErrorModal = document.querySelector(DOM_SELECTORS.UPDATE_COMMENT_POST_ERROR_MODAL); + const reportCommentPostedModal = document.querySelector(DOM_SELECTORS.REPORT_COMMENT_POSTED_MODAL); + const reportCommentPostErrorModal = document.querySelector(DOM_SELECTORS.REPORT_COMMENT_POST_ERROR_MODAL); + const confirmAbuseModal = document.querySelector(DOM_SELECTORS.REPORT_COMMENT_CONFIRMATION_MODAL); + const productCommentUpdatePostErrorMessage = document.querySelector(DOM_SELECTORS.UPDATE_COMMENT_POST_ERROR_MESSAGE); + const productCommentAbuseReportErrorMessage = document.querySelector(DOM_SELECTORS.REPORT_COMMENT_POST_ERROR_MESSAGE); + const productCommentPagination = document.querySelector(DOM_SELECTORS.PRODUCT_COMMENTS_LIST_PAGINATION); + const commentsListUrl = commentsList.dataset?.listCommentsUrl; + const updateCommentUsefulnessUrl = commentsList.dataset?.updateCommentUsefulnessUrl; + const reportCommentUrl = commentsList.dataset?.reportCommentUrl; + const commentPrototype = commentsList.dataset?.commentItemPrototype; + + hide(emptyProductComment); + + each('.comments-note .grade-stars', starRating); prestashop.on('updatedProduct', function() { - $('.product-comments-additional-info .grade-stars').rating(); + each('.product-comments-additional-info .grade-stars', starRating); }) document.addEventListener('updateRating', function() { - $('.comments-note .grade-stars').rating(); + each('.comments-note .grade-stars', starRating); }); - const updateCommentPostErrorModal = $('#update-comment-usefulness-post-error'); + const buildPagination = (paginationElement, { + onePageClick = () => {}, + currentPage = null, + items = null, + itemsOnPage = null, + }) => { + if (!paginationElement || !items || !itemsOnPage || !currentPage) { + throw new Error('Invalid pagination configuration'); + } + + const prevText = 'chevron_left'; + const nextText = 'chevron_right'; + + const calculateNumberOfPages = () => Math.ceil(items / itemsOnPage); + const shouldShowPagination = () => calculateNumberOfPages() > 1; + const shouldPrevBeActive = () => currentPage > 1; + const shouldNextBeActive = () => currentPage < calculateNumberOfPages(); + + const buildPaginationList = () => { + const paginationList = document.createElement('ul'); + paginationList.classList.add('pagination', 'mb-0'); + let paginationItems = ''; + paginationItems += buildPaginationPrev(); + + for (let i = 1; i <= calculateNumberOfPages(); i++) { + paginationItems += buildPaginationItem(i); + } + + paginationItems += buildPaginationNext(); + + paginationList.innerHTML = paginationItems; + + return paginationList; + } + + const buildPaginationItem = (page) => ` +
  • + ${page} +
  • + `; + + const buildPaginationPrev = () => ` +
  • + ${prevText} +
  • + `; + + const buildPaginationNext = () => ` +
  • + ${nextText} +
  • + `; + + const attachPaginationEvents = () => { + const pageElements = paginationElement.querySelectorAll('.js-page, .js-prev, .js-next'); + + each(pageElements, (pageElement) => { + eventHandlerOn(pageElement, 'click', (e) => { + e.preventDefault(); + e.stopPropagation(); + onePageClick(parseInt(pageElement.dataset.page)); + }); + }); + } + + const init = () => { + if (shouldShowPagination()) { + const paginationList = buildPaginationList(); + paginationElement.innerHTML = ''; + paginationElement.append(paginationList); + + attachPaginationEvents(); + } + } - const confirmAbuseModal = $('#report-comment-confirmation'); - const reportCommentPostErrorModal = $('#report-comment-post-error'); - const reportCommentPostedModal = $('#report-comment-posted'); + init(); + } - function showUpdatePostCommentErrorModal(errorMessage) { - $('#update-comment-usefulness-post-error-message').html(errorMessage); - updateCommentPostErrorModal.modal('show'); + const showUpdatePostCommentErrorModal = (errorMessage) => { + productCommentUpdatePostErrorMessage.innerHTML = errorMessage; + bootstrap.Modal.getOrCreateInstance(updateCommentPostErrorModal).show(); } - function showReportCommentErrorModal(errorMessage) { - $('#report-comment-post-error-message').html(errorMessage); - reportCommentPostErrorModal.modal('show'); + const showReportCommentErrorModal = (errorMessage) => { + productCommentAbuseReportErrorMessage.innerHTML = errorMessage; + bootstrap.Modal.getOrCreateInstance(reportCommentPostErrorModal).show(); } - function paginateComments(page) { - $.get(commentsListUrl, {page: page}, function(jsonResponse) { - if (jsonResponse.comments && jsonResponse.comments.length > 0) { - populateComments(jsonResponse.comments); - if (jsonResponse.comments_nb > jsonResponse.comments_per_page) { - $('#product-comments-list-pagination').pagination({ - currentPage: page, - items: jsonResponse.comments_nb, - itemsOnPage: jsonResponse.comments_per_page, - cssStyle: '', - prevText: 'chevron_left', - nextText: 'chevron_right', - useAnchors: false, - displayedPages: 2, - onPageClick: paginateComments - }); + + const updateCommentUsefulness = (comment, commentId, usefulness) => { + const { request } = useHttpRequest(updateCommentUsefulnessUrl, { + headers: { + accept: '*/*', + } + }); + + request + .query({ + id_product_comment: commentId, + usefulness: usefulness + }) + .post() + .json((jsonData) => { + if (jsonData) { + if (jsonData.success) { + const useFulValue = comment.querySelector('.js-useful-review-value'); + const notUseFulValue = comment.querySelector('.js-not-useful-review-value'); + + if (useFulValue) { + useFulValue.innerText = jsonData.usefulness; + } + + if (notUseFulValue) { + notUseFulValue.innerText = jsonData.total_usefulness - jsonData.usefulness; + } + } else { + showUpdatePostCommentErrorModal(`
    ${jsonData.error}
    `); + } } else { - $('#product-comments-list-pagination').hide(); + showUpdatePostCommentErrorModal(`
    ${productCommentUpdatePostErrorMessage}
    `); } - } else { - commentsList.html(''); - emptyProductComment.show(); - commentsList.append(emptyProductComment); + }) + .catch(() => { + showUpdatePostCommentErrorModal(`
    ${productCommentUpdatePostErrorMessage}
    `); + }); + } + + const confirmCommentAbuse = (commentId) => { + bootstrap.Modal.getOrCreateInstance(confirmAbuseModal).show(); + + eventHandlerOne(confirmAbuseModal, 'modal:confirm', (event) => { + const { confirm = false } = event; + + if (!confirm) { + return; } + + const { request } = useHttpRequest(reportCommentUrl, { + headers: { + accept: '*/*', + } + }); + + request + .query({ + id_product_comment: commentId, + }) + .post() + .json((jsonData) => { + if (jsonData) { + if (jsonData.success) { + bootstrap.Modal.getOrCreateInstance(reportCommentPostedModal).show(); + } else { + showReportCommentErrorModal(jsonData.error); + } + } else { + showReportCommentErrorModal(productCommentAbuseReportErrorMessage); + } + }); + }); } - function populateComments(comments) { - commentsList.html(''); - comments.forEach(addComment); - } + const addComment = (comment) => { + let commentTemplate = commentPrototype; + let customerName = comment.customer_name; - function addComment(comment) { - var commentTemplate = commentPrototype; - var customerName = comment.customer_name; if (!customerName) { customerName = comment.firstname+' '+comment.lastname; } + commentTemplate = commentTemplate.replace(/@COMMENT_ID@/, comment.id_product_comment); commentTemplate = commentTemplate.replace(/@PRODUCT_ID@/, comment.id_product); commentTemplate = commentTemplate.replace(/@CUSTOMER_NAME@/, customerName); @@ -107,64 +219,73 @@ jQuery(document).ready(function () { commentTemplate = commentTemplate.replace(/@COMMENT_NOT_USEFUL_ADVICES@/, (comment.total_usefulness - comment.usefulness)); commentTemplate = commentTemplate.replace(/@COMMENT_TOTAL_ADVICES@/, comment.total_usefulness); - const $comment = $(commentTemplate); - $('.grade-stars', $comment).rating({ - grade: comment.grade - }); - $('.js-useful-review', $comment).click(function(e) { - e.preventDefault(); - updateCommentUsefulness($comment, comment.id_product_comment, 1); - }); - $('.js-not-useful-review', $comment).click(function(e) { - e.preventDefault(); - updateCommentUsefulness($comment, comment.id_product_comment, 0); - }); - $('.js-report-abuse', $comment).click(function(e) { - e.preventDefault(); - confirmCommentAbuse(comment.id_product_comment); - }); + const commentElement = parseToHtml(commentTemplate); + const commentStars = commentElement.querySelector('.grade-stars'); + + starRating(commentStars, comment.grade); + + const btnUsefulReview = commentElement.querySelector('.js-useful-review'); + const btnNotUsefulReview = commentElement.querySelector('.js-not-useful-review'); + const btnReportAbuse = commentElement.querySelector('.js-report-abuse'); + + if (btnUsefulReview) { + eventHandlerOn(btnUsefulReview, 'click', (e) => { + e.preventDefault(); + updateCommentUsefulness(commentElement, comment.id_product_comment, 1); + }); + } + + if (btnNotUsefulReview) { + eventHandlerOn(btnNotUsefulReview, 'click', (e) => { + e.preventDefault(); + updateCommentUsefulness(commentElement, comment.id_product_comment, 0); + }); + } - commentsList.append($comment); + if (btnReportAbuse) { + eventHandlerOn(btnReportAbuse, 'click', (e) => { + e.preventDefault(); + confirmCommentAbuse(comment.id_product_comment); + }); + } + + commentsList.append(commentElement); } - function updateCommentUsefulness($comment, commentId, usefulness) { - $.post(updateCommentUsefulnessUrl, {id_product_comment: commentId, usefulness: usefulness}, function(jsonData){ - if (jsonData) { - if (jsonData.success) { - $('.js-useful-review-value', $comment).html(jsonData.usefulness); - $('.js-not-useful-review-value', $comment).html(jsonData.total_usefulness - jsonData.usefulness); - } else { - const decodedErrorMessage = $("
    ").html(jsonData.error).text(); - showUpdatePostCommentErrorModal(decodedErrorMessage); - } - } else { - showUpdatePostCommentErrorModal(productCommentUpdatePostErrorMessage); - } - }).fail(function() { - showUpdatePostCommentErrorModal(productCommentUpdatePostErrorMessage); - }); + const populateComments = (comments) => { + commentsList.innerHTML = ''; + comments.forEach(addComment); } - function confirmCommentAbuse(commentId) { - confirmAbuseModal.modal('show'); - confirmAbuseModal.one('modal:confirm', function(event, confirm) { - if (!confirm) { - return; + const paginateComments = (page) => { + const { request } = useHttpRequest(commentsListUrl, { + headers: { + accept: '*/*', } - $.post(reportCommentUrl, {id_product_comment: commentId}, function(jsonData){ - if (jsonData) { - if (jsonData.success) { - reportCommentPostedModal.modal('show'); + }); + + request + .query({ page }) + .get() + .json((jsonResponse) => { + if (jsonResponse.comments && jsonResponse.comments.length > 0) { + populateComments(jsonResponse.comments); + if (jsonResponse.comments_nb > jsonResponse.comments_per_page) { + buildPagination(productCommentPagination, { + onePageClick: paginateComments, + currentPage: page, + items: jsonResponse.comments_nb, + itemsOnPage: jsonResponse.comments_per_page, + }); } else { - showReportCommentErrorModal(jsonData.error); + hide(productCommentPagination); } } else { - showReportCommentErrorModal(productCommentAbuseReportErrorMessage); + commentsList.innerHTML = ''; + show(emptyProductComment); + commentsList.append(emptyProductComment); } - }).fail(function() { - showReportCommentErrorModal(productCommentAbuseReportErrorMessage); - }); - }) + }) } paginateComments(1); diff --git a/modules/productcomments/views/js/post-comment.js b/modules/productcomments/views/js/post-comment.js index c81bee28..6d413bb7 100644 --- a/modules/productcomments/views/js/post-comment.js +++ b/modules/productcomments/views/js/post-comment.js @@ -8,27 +8,33 @@ DOMReady(() => { POST_COMMENT_FORM: '#post-product-comment-form', POST_COMMENT_FORM_INPUTS: '#post-product-comment-form input[type="text"]', POST_COMMENT_FORM_TEXTAREA: '#post-product-comment-form textarea', - POST_COMMENT_FORM_CRITERION_RATING: '#post-product-comment-form .criterion-rating input', + POST_COMMENT_FORM_CRITERION_RATING: '#post-product-comment-form .js-grade-stars-input input', + POST_COMMENT_FORM_CRITERION_RATING_GRADE: '#post-product-comment-form .js-grade-stars-input', }; const postCommentModal = document.querySelector(DOM_SELECTORS.POST_COMMENT_MODAL); const commentPostedModal = document.querySelector(DOM_SELECTORS.COMMENT_POSTED_MODAL); const commentPostErrorModal = document.querySelector(DOM_SELECTORS.COMMENT_POST_ERROR_MODAL); const commentForm = document.querySelector(DOM_SELECTORS.POST_COMMENT_FORM); + const criterionRatingGrades = document.querySelectorAll(DOM_SELECTORS.POST_COMMENT_FORM_CRITERION_RATING_GRADE); const getCommentPostModalInstance = () => bootstrap.Modal.getOrCreateInstance(postCommentModal); const getCommentPostedModalInstance = () => bootstrap.Modal.getOrCreateInstance(commentPostedModal); const getCommentPostErrorModalInstance = () => bootstrap.Modal.getOrCreateInstance(commentPostErrorModal); + const initCriterionRating = () => { + each(criterionRatingGrades, (criterionRatingGrade) => { + const inputName = criterionRatingGrade.dataset?.inputName; + + starRatingInput(criterionRatingGrade, inputName, 5); + }); + } + const clearPostCommentForm = () => { each(`${DOM_SELECTORS.POST_COMMENT_FORM_INPUTS}, ${DOM_SELECTORS.POST_COMMENT_FORM_TEXTAREA}`, (formElement) => { formElement.value = ''; formElement.classList.remove('is-invalid'); }); - each(DOM_SELECTORS.POST_COMMENT_FORM_CRITERION_RATING, (formElement) => { - formElement.value = 5; - const event = new Event('change'); - formElement.dispatchEvent(event); - }); + initCriterionRating(); } const showPostErrorModal = (errorMessage) => { @@ -86,22 +92,22 @@ DOMReady(() => { if (jsonData.errors) { const { errors } = jsonData; const errorList = ` -
      +
        ${errors.map((error) => `
      • ${error}
      • `).join('')}
      `; showPostErrorModal(errorList); } else if (jsonData.error) { - showPostErrorModal(jsonData.error); + showPostErrorModal(`

      ${jsonData.error}

      `); } } } else { - showPostErrorModal(`

      ${productCommentPostErrorMessage}

      `); + showPostErrorModal(`

      ${productCommentPostErrorMessage}

      `); } }) .catch(() => { - showPostErrorModal(`

      ${productCommentPostErrorMessage}

      `); + showPostErrorModal(`

      ${productCommentPostErrorMessage}

      `); }); } @@ -137,6 +143,8 @@ DOMReady(() => { return isValid; } + initCriterionRating(); + eventHandlerOn(document, 'click', DOM_SELECTORS.BTN_POST_MODAL_BTN, handleClickPostModalBtn); eventHandlerOn(postCommentModal, 'hidden.bs.modal', handlePostCommentModalHidden); eventHandlerOn(commentForm, 'submit', handleSubmitCommentForm); diff --git a/modules/productcomments/views/js/productListingComments.js b/modules/productcomments/views/js/productListingComments.js index 0eb49bfd..45216984 100644 --- a/modules/productcomments/views/js/productListingComments.js +++ b/modules/productcomments/views/js/productListingComments.js @@ -116,7 +116,7 @@ var productListingComments = (function () { var $self = $(this); if (productData.comments_nb > 0) { - $self.find(DOMStrings.productListReviewsStarsContainer).rating({ grade: productData.average_grade, starWidth: 16 }); + starRating($self.find(DOMStrings.productListReviewsStarsContainer)[0], productData.average_grade); $self.find(DOMStrings.productListReviewsNumberOfComments).text('(' + productData.comments_nb + ')'); $self.closest(DOMStrings.productContainer).addClass(DOMClasses.hasReviews); $self.css('visibility', 'visible'); diff --git a/modules/productcomments/views/templates/hook/alert-modal.tpl b/modules/productcomments/views/templates/hook/alert-modal.tpl index 3f9a3cb5..ee1c05d7 100644 --- a/modules/productcomments/views/templates/hook/alert-modal.tpl +++ b/modules/productcomments/views/templates/hook/alert-modal.tpl @@ -26,15 +26,6 @@ {assign var='icon' value=$icon|default:'check_circle'} {assign var='modal_message' value=$modal_message|default:''} - -