diff options
| author | qsm-odoo <qsm@odoo.com> | 2019-02-06 14:29:07 +0000 |
|---|---|---|
| committer | qsm-odoo <qsm@odoo.com> | 2019-02-26 17:09:22 +0000 |
| commit | 27fca749af30d7dce448f6632132422df8d8a7c1 (patch) | |
| tree | c644db5efbb12cde722d059d250a83ec0de8aed7 /addons | |
| parent | c3da83b40a6431f607cf69b8c2a7a922a6e5b52c (diff) | |
[REF] *: use 'frontend' system in non-website apps
* account, auth_signup, payment, portal, project, sale, sale_management,
web_unsplash, website, website_mail, website_rating, website_sale
This commit does probably not do what is stated for all non-website apps
but it is a first step. It also uses the system in apps which could have
already used it but did not.
Part of https://github.com/odoo/odoo/pull/29442
task-1932066
Diffstat (limited to 'addons')
17 files changed, 1342 insertions, 1185 deletions
diff --git a/addons/account/static/src/js/account_portal_sidebar.js b/addons/account/static/src/js/account_portal_sidebar.js index 83fdf7dccae..92a31e69fe5 100644 --- a/addons/account/static/src/js/account_portal_sidebar.js +++ b/addons/account/static/src/js/account_portal_sidebar.js @@ -1,40 +1,25 @@ -odoo.define('account.AccountPortalSidebar.instance', function (require) { -"use strict"; - -require('web.dom_ready'); -var AccountPortalSidebar = require('account.AccountPortalSidebar'); - -if (!$('.o_portal_invoice_sidebar').length) { - return $.Deferred().reject("DOM doesn't contain '.o_portal_invoice_sidebar'"); -} - -var account_portal_sidebar = new AccountPortalSidebar(); -return account_portal_sidebar.attachTo($('.o_portal_invoice_sidebar')).then(function () { - return account_portal_sidebar; -}); -}); - -//============================================================================== - odoo.define('account.AccountPortalSidebar', function (require) { -"use strict"; +'use strict'; +var publicWidget = require('web.public.widget'); var PortalSidebar = require('portal.PortalSidebar'); -var AccountPortalSidebar = PortalSidebar.extend({ +publicWidget.registry.AccountPortalSidebar = PortalSidebar.extend({ + selector: '.o_portal_invoice_sidebar', events: { 'click .o_portal_invoice_print': '_onPrintInvoice', }, + /** * @override */ start: function () { - var self = this; - this._super.apply(this, arguments); + var def = this._super.apply(this, arguments); var $invoiceHtml = this.$el.find('iframe#invoice_html'); - var updateIframeSize = self._updateIframeSize.bind(self, $invoiceHtml); + var updateIframeSize = this._updateIframeSize.bind(this, $invoiceHtml); $invoiceHtml.on('load', updateIframeSize); $(window).on('resize', updateIframeSize); + return def; }, //-------------------------------------------------------------------------- @@ -64,7 +49,4 @@ var AccountPortalSidebar = PortalSidebar.extend({ this._printIframeContent(href); }, }); - - -return AccountPortalSidebar; }); diff --git a/addons/auth_signup/static/src/js/signup.js b/addons/auth_signup/static/src/js/signup.js index 567b2d612f1..366d4cef124 100644 --- a/addons/auth_signup/static/src/js/signup.js +++ b/addons/auth_signup/static/src/js/signup.js @@ -1,16 +1,25 @@ odoo.define('auth_signup.signup', function (require) { - 'use strict'; +'use strict'; -require('web.dom_ready'); +var publicWidget = require('web.public.widget'); -// Disable 'Sign Up' button to prevent user form continuous clicking -if ($('.oe_signup_form').length > 0) { - $('.oe_signup_form').on('submit', function (ev) { - var $form = $(ev.currentTarget); - var $btn = $form.find('.oe_login_buttons > button[type="submit"]'); +publicWidget.registry.SignUpForm = publicWidget.Widget.extend({ + selector: '.oe_signup_form', + events: { + 'submit': '_onSubmit', + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onSubmit: function () { + var $btn = this.$('.oe_login_buttons > button[type="submit"]'); $btn.attr('disabled', 'disabled'); $btn.prepend('<i class="fa fa-refresh fa-spin"/> '); - }); -} - + }, +}); }); diff --git a/addons/payment/static/src/js/payment_form.js b/addons/payment/static/src/js/payment_form.js index 7494f50bf03..6bbe4604dd4 100644 --- a/addons/payment/static/src/js/payment_form.js +++ b/addons/payment/static/src/js/payment_form.js @@ -1,234 +1,163 @@ odoo.define('payment.payment_form', function (require) { - "use strict"; - - var ajax = require('web.ajax'); - var config = require('web.config'); - var core = require('web.core'); - var dom = require('web.dom'); - var Dialog = require("web.Dialog"); - var Widget = require("web.Widget"); - var rpc = require("web.rpc"); - var _t = core._t; - - var PaymentForm = Widget.extend({ - - events: { - 'click #o_payment_form_pay': 'payEvent', - 'click #o_payment_form_add_pm': 'addPmEvent', - 'click button[name="delete_pm"]': 'deletePmEvent', - 'click .o_payment_acquirer_select': 'radioClickEvent', - 'click .o_payment_form_pay_icon_more': 'onClickMorePaymentIcon', - }, - - init: function(parent, options) { - this._super.apply(this, arguments); - this.options = _.extend(options || {}, { - }); - - // TODO simplify this using the 'async' keyword in the events - // property definition as soon as this widget is converted in - // frontend widget. - this.payEvent = dom.makeButtonHandler(this.payEvent); - }, - - start: function () { - this.updateNewPaymentDisplayStatus(); - $('[data-toggle="tooltip"]').tooltip(); - }, - - payEvent: function (ev) { - ev.preventDefault(); - var form = this.el; - var checked_radio = this.$('input[type="radio"]:checked'); - var self = this; - var button = ev.target; - - // first we check that the user has selected a payment method - if (checked_radio.length === 1) { - checked_radio = checked_radio[0]; - - // we retrieve all the input inside the acquirer form and 'serialize' them to an indexed array - var acquirer_id = this.getAcquirerIdFromRadio(checked_radio); - var acquirer_form = false; - if (this.isNewPaymentRadio(checked_radio)) { - acquirer_form = this.$('#o_payment_add_token_acq_' + acquirer_id); - } else { - acquirer_form = this.$('#o_payment_form_acq_' + acquirer_id); - } - var inputs_form = $('input', acquirer_form); - var ds = $('input[name="data_set"]', acquirer_form)[0]; - - // if the user is adding a new payment - if (this.isNewPaymentRadio(checked_radio)) { - if (this.options.partnerId === undefined) { - console.warn('payment_form: unset partner_id when adding new token; things could go wrong'); - } - var form_data = this.getFormData(inputs_form); - var wrong_input = false; - - inputs_form.toArray().forEach(function (element) { - //skip the check of non visible inputs - if ($(element).attr('type') == 'hidden') { - return true; - } - $(element).closest('div.form-group').removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid'); - $(element).siblings( ".o_invalid_field" ).remove(); - //force check of forms validity (useful for Firefox that refill forms automatically on f5) - $(element).trigger("focusout"); - if (element.dataset.isRequired && element.value.length === 0) { - $(element).closest('div.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid'); - $(element).closest('div.form-group').append('<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>'); - wrong_input = true; - } - else if ($(element).closest('div.form-group').hasClass('o_has_error')) { - wrong_input = true; - $(element).closest('div.form-group').append('<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>'); - } - }); - - if (wrong_input) { - return; - } - - $(button).attr('disabled', true); - $(button).children('.fa-plus-circle').removeClass('fa-plus-circle') - $(button).prepend('<span class="o_loader"><i class="fa fa-refresh fa-spin"></i> </span>'); - - var verify_validity = this.$el.find('input[name="verify_validity"]'); +"use strict"; + +var core = require('web.core'); +var Dialog = require('web.Dialog'); +var publicWidget = require('web.public.widget'); + +var _t = core._t; + +publicWidget.registry.PaymentForm = publicWidget.Widget.extend({ + selector: '.o_payment_form', + events: { + 'click #o_payment_form_pay': 'async payEvent', + 'click #o_payment_form_add_pm': 'addPmEvent', + 'click button[name="delete_pm"]': 'deletePmEvent', + 'click .o_payment_form_pay_icon_more': 'onClickMorePaymentIcon', + 'click .o_payment_acquirer_select': 'radioClickEvent', + }, + + /** + * @override + */ + start: function () { + this.options = _.extend(this.$el.data(), this.options); + this.updateNewPaymentDisplayStatus(); + $('[data-toggle="tooltip"]').tooltip(); + return this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @param {string} title + * @param {string} message + */ + displayError: function (title, message) { + var $checkedRadio = this.$('input[type="radio"]:checked'), + acquirerID = this.getAcquirerIdFromRadio($checkedRadio[0]); + var $acquirerForm; + if (this.isNewPaymentRadio($checkedRadio[0])) { + $acquirerForm = this.$('#o_payment_add_token_acq_' + acquirerID); + } + else if (this.isFormPaymentRadio($checkedRadio[0])) { + $acquirerForm = this.$('#o_payment_form_acq_' + acquirerID); + } - if (verify_validity.length>0) { - form_data.verify_validity = verify_validity[0].value === "1"; - } + if ($checkedRadio.length === 0) { + return new Dialog(null, { + title: _t('Error: ') + _.str.escapeHTML(title), + size: 'medium', + $content: "<p>" + (_.str.escapeHTML(message) || "") + "</p>" , + buttons: [ + {text: _t('Ok'), close: true}]}).open(); + } else { + // removed if exist error message + this.$('#payment_error').remove(); + var messageResult = '<div class="alert alert-danger mb4" id="payment_error">'; + if (title != '') { + messageResult = messageResult + '<b>' + _.str.escapeHTML(title) + ':</b></br>'; + } + messageResult = messageResult + _.str.escapeHTML(message) + '</div>'; + $acquirerForm.append(messageResult); + } + }, + /** + * @private + * @param {DOMElement} element + */ + getAcquirerIdFromRadio: function (element) { + return $(element).data('acquirer-id'); + }, + /** + * @private + * @param {jQuery} $form + */ + getFormData: function ($form) { + var unindexed_array = $form.serializeArray(); + var indexed_array = {}; + + $.map(unindexed_array, function (n, i) { + indexed_array[n.name] = n.value; + }); + return indexed_array; + }, + /** + * @private + * @param {DOMElement} element + */ + isFormPaymentRadio: function (element) { + return $(element).data('form-payment') === 'True'; + }, + /** + * @private + * @param {DOMElement} element + */ + isNewPaymentRadio: function (element) { + return $(element).data('s2s-payment') === 'True'; + }, + /** + * @private + */ + updateNewPaymentDisplayStatus: function () { + var checked_radio = this.$('input[type="radio"]:checked'); + // we hide all the acquirers form + this.$('[id*="o_payment_add_token_acq_"]').addClass('d-none'); + this.$('[id*="o_payment_form_acq_"]').addClass('d-none'); + if (checked_radio.length !== 1) { + return; + } + checked_radio = checked_radio[0]; + var acquirer_id = this.getAcquirerIdFromRadio(checked_radio); - // do the call to the route stored in the 'data_set' input of the acquirer form, the data must be called 'create-route' - return ajax.jsonRpc(ds.dataset.createRoute, 'call', form_data).then(function (data) { - // if the server has returned true - if (data.result) { - // and it need a 3DS authentication - if (data['3d_secure'] !== false) { - // then we display the 3DS page to the user - $("body").html(data['3d_secure']); - } - else { - checked_radio.value = data.id; // set the radio value to the new card id - form.submit(); - return $.Deferred(); - } - } - // if the server has returned false, we display an error - else { - if (data.error) { - self.displayError( - '', - data.error); - } else { // if the server doesn't provide an error message - self.displayError( - _t('Server Error'), - _t('e.g. Your credit card details are wrong. Please verify.')); - } - } - // here we remove the 'processing' icon from the 'add a new payment' button - $(button).attr('disabled', false); - $(button).children('.fa').addClass('fa-plus-circle') - $(button).find('span.o_loader').remove(); - }).fail(function (error, event) { - // if the rpc fails, pretty obvious - $(button).attr('disabled', false); - $(button).children('.fa').addClass('fa-plus-circle') - $(button).find('span.o_loader').remove(); + // if we clicked on an add new payment radio, display its form + if (this.isNewPaymentRadio(checked_radio)) { + this.$('#o_payment_add_token_acq_' + acquirer_id).removeClass('d-none'); + } + else if (this.isFormPaymentRadio(checked_radio)) { + this.$('#o_payment_form_acq_' + acquirer_id).removeClass('d-none'); + } + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {Event} ev + */ + payEvent: function (ev) { + ev.preventDefault(); + var form = this.el; + var checked_radio = this.$('input[type="radio"]:checked'); + var self = this; + var button = ev.target; + + // first we check that the user has selected a payment method + if (checked_radio.length === 1) { + checked_radio = checked_radio[0]; - self.displayError( - _t('Server Error'), - _t("We are not able to add your payment method at the moment.") + - error.data.message - ); - }); - } - // if the user is going to pay with a form payment, then - else if (this.isFormPaymentRadio(checked_radio)) { - var $tx_url = this.$el.find('input[name="prepare_tx_url"]'); - // if there's a prepare tx url set - if ($tx_url.length === 1) { - // if the user wants to save his credit card info - var form_save_token = acquirer_form.find('input[name="o_payment_form_save_token"]').prop('checked'); - // then we call the route to prepare the transaction - return ajax.jsonRpc($tx_url[0].value, 'call', { - 'acquirer_id': parseInt(acquirer_id), - 'save_token': form_save_token, - 'access_token': self.options.accessToken, - 'success_url': self.options.successUrl, - 'error_url': self.options.errorUrl, - 'callback_method': self.options.callbackMethod, - 'order_id': self.options.orderId, - }).then(function (result) { - if (result) { - // if the server sent us the html form, we create a form element - var newForm = document.createElement('form'); - newForm.setAttribute("method", "post"); // set it to post - newForm.setAttribute("provider", checked_radio.dataset.provider); - newForm.hidden = true; // hide it - newForm.innerHTML = result; // put the html sent by the server inside the form - var action_url = $(newForm).find('input[name="data_set"]').data('actionUrl'); - newForm.setAttribute("action", action_url); // set the action url - $(document.getElementsByTagName('body')[0]).append(newForm); // append the form to the body - $(newForm).find('input[data-remove-me]').remove(); // remove all the input that should be removed - if(action_url) { - newForm.submit(); // and finally submit the form - return $.Deferred(); - } - } - else { - self.displayError( - _t('Server Error'), - _t("We are not able to redirect you to the payment form.") - ); - } - }).fail(function (error, event) { - self.displayError( - _t('Server Error'), - _t("We are not able to redirect you to the payment form. ") + - error.data.message - ); - }); - } - else { - // we append the form to the body and send it. - this.displayError( - _t("Cannot set-up the payment"), - _t("We're unable to process your payment.") - ); - } - } - else { // if the user is using an old payment then we just submit the form - form.submit(); - return $.Deferred(); - } - } - else { - this.displayError( - _t('No payment method selected'), - _t('Please select a payment method.') - ); + // we retrieve all the input inside the acquirer form and 'serialize' them to an indexed array + var acquirer_id = this.getAcquirerIdFromRadio(checked_radio); + var acquirer_form = false; + if (this.isNewPaymentRadio(checked_radio)) { + acquirer_form = this.$('#o_payment_add_token_acq_' + acquirer_id); + } else { + acquirer_form = this.$('#o_payment_form_acq_' + acquirer_id); } - }, - // event handler when clicking on the button to add a new payment method - addPmEvent: function (ev) { - ev.stopPropagation(); - ev.preventDefault(); - var checked_radio = this.$('input[type="radio"]:checked'); - var self = this; - var button = ev.target; + var inputs_form = $('input', acquirer_form); + var ds = $('input[name="data_set"]', acquirer_form)[0]; - // we check if the user has selected a 'add a new payment' option - if (checked_radio.length === 1 && this.isNewPaymentRadio(checked_radio[0])) { - // we retrieve which acquirer is used - checked_radio = checked_radio[0]; - var acquirer_id = this.getAcquirerIdFromRadio(checked_radio); - var acquirer_form = this.$('#o_payment_add_token_acq_' + acquirer_id); - // we retrieve all the input inside the acquirer form and 'serialize' them to an indexed array - var inputs_form = $('input', acquirer_form); + // if the user is adding a new payment + if (this.isNewPaymentRadio(checked_radio)) { + if (this.options.partnerId === undefined) { + console.warn('payment_form: unset partner_id when adding new token; things could go wrong'); + } var form_data = this.getFormData(inputs_form); - var ds = $('input[name="data_set"]', acquirer_form)[0]; var wrong_input = false; inputs_form.toArray().forEach(function (element) { @@ -242,30 +171,34 @@ odoo.define('payment.payment_form', function (require) { $(element).trigger("focusout"); if (element.dataset.isRequired && element.value.length === 0) { $(element).closest('div.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid'); - var message = '<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>'; - $(element).closest('div.form-group').append(message); + $(element).closest('div.form-group').append('<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>'); wrong_input = true; } else if ($(element).closest('div.form-group').hasClass('o_has_error')) { wrong_input = true; - var message = '<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>'; - $(element).closest('div.form-group').append(message); + $(element).closest('div.form-group').append('<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>'); } }); if (wrong_input) { return; } - // We add a 'processing' icon into the 'add a new payment' button + $(button).attr('disabled', true); - $(button).children('.fa-plus-circle').removeClass('fa-plus-circle') + $(button).children('.fa-plus-circle').removeClass('fa-plus-circle'); $(button).prepend('<span class="o_loader"><i class="fa fa-refresh fa-spin"></i> </span>'); - // we force the check when adding a card trough here - form_data.verify_validity = true; + var verify_validity = this.$el.find('input[name="verify_validity"]'); + + if (verify_validity.length>0) { + form_data.verify_validity = verify_validity[0].value === "1"; + } // do the call to the route stored in the 'data_set' input of the acquirer form, the data must be called 'create-route' - ajax.jsonRpc(ds.dataset.createRoute, 'call', form_data).then( function (data) { + return this._rpc({ + route: ds.dataset.createRoute, + params: form_data, + }).then(function (data) { // if the server has returned true if (data.result) { // and it need a 3DS authentication @@ -273,15 +206,10 @@ odoo.define('payment.payment_form', function (require) { // then we display the 3DS page to the user $("body").html(data['3d_secure']); } - // if it doesn't require 3DS else { - // we just go to the return_url or reload the page - if (form_data.return_url) { - window.location = form_data.return_url; - } - else { - window.location.reload(); - } + checked_radio.value = data.id; // set the radio value to the new card id + form.submit(); + return $.Deferred(); } } // if the server has returned false, we display an error @@ -298,186 +226,296 @@ odoo.define('payment.payment_form', function (require) { } // here we remove the 'processing' icon from the 'add a new payment' button $(button).attr('disabled', false); - $(button).children('.fa').addClass('fa-plus-circle') + $(button).children('.fa').addClass('fa-plus-circle'); $(button).find('span.o_loader').remove(); }).fail(function (error, event) { // if the rpc fails, pretty obvious $(button).attr('disabled', false); - $(button).children('.fa').addClass('fa-plus-circle') + $(button).children('.fa').addClass('fa-plus-circle'); $(button).find('span.o_loader').remove(); self.displayError( - _t('Server error'), - _t("We are not able to add your payment method at the moment.</p>") + - error.data.message + _t('Server Error'), + _t("We are not able to add your payment method at the moment.") + + error.data.message ); }); } - else { - this.displayError( - _t('No payment method selected'), - _t('Please select the option to add a new payment method.') - ); - } - }, - // event handler when clicking on a button to delete a payment method - deletePmEvent: function (ev) { - ev.stopPropagation(); - ev.preventDefault(); - var self = this; - var pm_id = parseInt(ev.target.value); - - var tokenDelete = function () { - rpc.query({ - model: 'payment.token', - method: 'unlink', - args: [pm_id], - }) - .then(function (result) { - if ( result === true) { - ev.target.closest('div').remove(); + // if the user is going to pay with a form payment, then + else if (this.isFormPaymentRadio(checked_radio)) { + var $tx_url = this.$el.find('input[name="prepare_tx_url"]'); + // if there's a prepare tx url set + if ($tx_url.length === 1) { + // if the user wants to save his credit card info + var form_save_token = acquirer_form.find('input[name="o_payment_form_save_token"]').prop('checked'); + // then we call the route to prepare the transaction + return this._rpc({ + route: $tx_url[0].value, + params: { + 'acquirer_id': parseInt(acquirer_id), + 'save_token': form_save_token, + 'access_token': self.options.accessToken, + 'success_url': self.options.successUrl, + 'error_url': self.options.errorUrl, + 'callback_method': self.options.callbackMethod, + 'order_id': self.options.orderId, + }, + }).then(function (result) { + if (result) { + // if the server sent us the html form, we create a form element + var newForm = document.createElement('form'); + newForm.setAttribute("method", "post"); // set it to post + newForm.setAttribute("provider", checked_radio.dataset.provider); + newForm.hidden = true; // hide it + newForm.innerHTML = result; // put the html sent by the server inside the form + var action_url = $(newForm).find('input[name="data_set"]').data('actionUrl'); + newForm.setAttribute("action", action_url); // set the action url + $(document.getElementsByTagName('body')[0]).append(newForm); // append the form to the body + $(newForm).find('input[data-remove-me]').remove(); // remove all the input that should be removed + if(action_url) { + newForm.submit(); // and finally submit the form + return $.Deferred(); + } } - }, function () { + else { + self.displayError( + _t('Server Error'), + _t("We are not able to redirect you to the payment form.") + ); + } + }).fail(function (error, event) { self.displayError( _t('Server Error'), - _t("We are not able to delete your payment method at the moment.") + _t("We are not able to redirect you to the payment form. ") + + error.data.message ); }); - }; + } + else { + // we append the form to the body and send it. + this.displayError( + _t("Cannot set-up the payment"), + _t("We're unable to process your payment.") + ); + } + } + else { // if the user is using an old payment then we just submit the form + form.submit(); + return $.Deferred(); + } + } + else { + this.displayError( + _t('No payment method selected'), + _t('Please select a payment method.') + ); + } + }, + /** + * Called when clicking on the button to add a new payment method. + * + * @private + * @param {Event} ev + */ + addPmEvent: function (ev) { + ev.stopPropagation(); + ev.preventDefault(); + var checked_radio = this.$('input[type="radio"]:checked'); + var self = this; + var button = ev.target; + + // we check if the user has selected a 'add a new payment' option + if (checked_radio.length === 1 && this.isNewPaymentRadio(checked_radio[0])) { + // we retrieve which acquirer is used + checked_radio = checked_radio[0]; + var acquirer_id = this.getAcquirerIdFromRadio(checked_radio); + var acquirer_form = this.$('#o_payment_add_token_acq_' + acquirer_id); + // we retrieve all the input inside the acquirer form and 'serialize' them to an indexed array + var inputs_form = $('input', acquirer_form); + var form_data = this.getFormData(inputs_form); + var ds = $('input[name="data_set"]', acquirer_form)[0]; + var wrong_input = false; + + inputs_form.toArray().forEach(function (element) { + //skip the check of non visible inputs + if ($(element).attr('type') == 'hidden') { + return true; + } + $(element).closest('div.form-group').removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid'); + $(element).siblings( ".o_invalid_field" ).remove(); + //force check of forms validity (useful for Firefox that refill forms automatically on f5) + $(element).trigger("focusout"); + if (element.dataset.isRequired && element.value.length === 0) { + $(element).closest('div.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid'); + var message = '<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>'; + $(element).closest('div.form-group').append(message); + wrong_input = true; + } + else if ($(element).closest('div.form-group').hasClass('o_has_error')) { + wrong_input = true; + var message = '<div style="color: red" class="o_invalid_field" aria-invalid="true">' + _.str.escapeHTML("The value is invalid.") + '</div>'; + $(element).closest('div.form-group').append(message); + } + }); + + if (wrong_input) { + return; + } + // We add a 'processing' icon into the 'add a new payment' button + $(button).attr('disabled', true); + $(button).children('.fa-plus-circle').removeClass('fa-plus-circle'); + $(button).prepend('<span class="o_loader"><i class="fa fa-refresh fa-spin"></i> </span>'); + + // we force the check when adding a card trough here + form_data.verify_validity = true; + + // do the call to the route stored in the 'data_set' input of the acquirer form, the data must be called 'create-route' + this._rpc({ + route: ds.dataset.createRoute, + params: form_data, + }).then(function (data) { + // if the server has returned true + if (data.result) { + // and it need a 3DS authentication + if (data['3d_secure'] !== false) { + // then we display the 3DS page to the user + $("body").html(data['3d_secure']); + } + // if it doesn't require 3DS + else { + // we just go to the return_url or reload the page + if (form_data.return_url) { + window.location = form_data.return_url; + } + else { + window.location.reload(); + } + } + } + // if the server has returned false, we display an error + else { + if (data.error) { + self.displayError( + '', + data.error); + } else { // if the server doesn't provide an error message + self.displayError( + _t('Server Error'), + _t('e.g. Your credit card details are wrong. Please verify.')); + } + } + // here we remove the 'processing' icon from the 'add a new payment' button + $(button).attr('disabled', false); + $(button).children('.fa').addClass('fa-plus-circle'); + $(button).find('span.o_loader').remove(); + }).fail(function (error, event) { + // if the rpc fails, pretty obvious + $(button).attr('disabled', false); + $(button).children('.fa').addClass('fa-plus-circle'); + $(button).find('span.o_loader').remove(); - rpc.query({ + self.displayError( + _t('Server error'), + _t("We are not able to add your payment method at the moment.</p>") + + error.data.message + ); + }); + } + else { + this.displayError( + _t('No payment method selected'), + _t('Please select the option to add a new payment method.') + ); + } + }, + /** + * Called when clicking on a button to delete a payment method. + * + * @private + * @param {Event} ev + */ + deletePmEvent: function (ev) { + ev.stopPropagation(); + ev.preventDefault(); + var self = this; + var pm_id = parseInt(ev.target.value); + + var tokenDelete = function () { + self._rpc({ model: 'payment.token', - method: 'get_linked_records', + method: 'unlink', args: [pm_id], }).then(function (result) { - if (result[pm_id].length > 0) { - // if there's records linked to this payment method - var content = ''; - result[pm_id].forEach(function (sub) { - content += '<p><a href="' + sub.url + '" title="' + sub.description + '">' + sub.name + '</a><p/>'; - }); - - content = $('<div>').html(_t('<p>This card is currently linked to the following records:<p/>') + content); - // Then we display the list of the records and ask the user if he really want to remove the payment method. - new Dialog(self, { - title: _t('Warning!'), - size: 'medium', - $content: content, - buttons: [ - {text: _t('Confirm Deletion'), classes: 'btn-primary', close: true, click: tokenDelete}, - {text: _t('Cancel'), close: true}] - }).open(); - } - else { - // if there's no records linked to this payment method, then we delete it - tokenDelete(); + if (result === true) { + ev.target.closest('div').remove(); } - }, function (err, event) { + }, function () { self.displayError( _t('Server Error'), - _t("We are not able to delete your payment method at the moment.") + err.data.message + _t("We are not able to delete your payment method at the moment.") ); }); - }, - - // event handler when clicking on 'and more' to show more payment icon - onClickMorePaymentIcon: function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - var $listItems = $(ev.currentTarget).parents('ul').children('li'); - var $moreItem = $(ev.currentTarget).parents('li'); - $listItems.removeClass('d-none'); - $moreItem.addClass('d-none'); - }, - - // event handler when clicking on a radio button - radioClickEvent: function (ev) { - // radio button checked when we click on entire zone(body) of the payment acquirer - $(ev.currentTarget).find('input[type="radio"]').prop("checked", true); - this.updateNewPaymentDisplayStatus(); - }, - updateNewPaymentDisplayStatus: function () - { - var checked_radio = this.$('input[type="radio"]:checked'); - // we hide all the acquirers form - this.$('[id*="o_payment_add_token_acq_"]').addClass('d-none'); - this.$('[id*="o_payment_form_acq_"]').addClass('d-none'); - if (checked_radio.length !== 1) { - return; - } - checked_radio = checked_radio[0]; - var acquirer_id = this.getAcquirerIdFromRadio(checked_radio); - - // if we clicked on an add new payment radio, display its form - if (this.isNewPaymentRadio(checked_radio)) { - this.$('#o_payment_add_token_acq_' + acquirer_id).removeClass('d-none'); - } - else if (this.isFormPaymentRadio(checked_radio)) { - this.$('#o_payment_form_acq_' + acquirer_id).removeClass('d-none'); - } - }, - isNewPaymentRadio: function (element) { - return $(element).data('s2s-payment') === 'True'; - }, - isFormPaymentRadio: function (element) { - return $(element).data('form-payment') === 'True'; - }, - getAcquirerIdFromRadio: function (element) { - return $(element).data('acquirer-id'); - }, - displayError: function (title, message) { - var $checkedRadio = this.$('input[type="radio"]:checked'), - acquirerID = this.getAcquirerIdFromRadio($checkedRadio[0]); - var $acquirerForm; - if (this.isNewPaymentRadio($checkedRadio[0])) { - $acquirerForm = this.$('#o_payment_add_token_acq_' + acquirerID); - } - else if (this.isFormPaymentRadio($checkedRadio[0])) { - $acquirerForm = this.$('#o_payment_form_acq_' + acquirerID); - } + }; + + this._rpc({ + model: 'payment.token', + method: 'get_linked_records', + args: [pm_id], + }).then(function (result) { + if (result[pm_id].length > 0) { + // if there's records linked to this payment method + var content = ''; + result[pm_id].forEach(function (sub) { + content += '<p><a href="' + sub.url + '" title="' + sub.description + '">' + sub.name + '</a><p/>'; + }); - if ($checkedRadio.length === 0) { - return new Dialog(null, { - title: _t('Error: ') + _.str.escapeHTML(title), + content = $('<div>').html(_t('<p>This card is currently linked to the following records:<p/>') + content); + // Then we display the list of the records and ask the user if he really want to remove the payment method. + new Dialog(self, { + title: _t('Warning!'), size: 'medium', - $content: "<p>" + (_.str.escapeHTML(message) || "") + "</p>" , + $content: content, buttons: [ - {text: _t('Ok'), close: true}]}).open(); - } else { - // removed if exist error message - this.$('#payment_error').remove(); - var messageResult = '<div class="alert alert-danger mb4" id="payment_error">'; - if (title != '') { - messageResult = messageResult + '<b>' + _.str.escapeHTML(title) + ':</b></br>'; - } - messageResult = messageResult + _.str.escapeHTML(message) + '</div>'; - $acquirerForm.append(messageResult); + {text: _t('Confirm Deletion'), classes: 'btn-primary', close: true, click: tokenDelete}, + {text: _t('Cancel'), close: true}] + }).open(); } - }, - getFormData: function ($form) { - var unindexed_array = $form.serializeArray(); - var indexed_array = {}; - - $.map(unindexed_array, function (n, i) { - indexed_array[n.name] = n.value; - }); - return indexed_array; - }, - }); - - $(function () { - // TODO move this to another module, requiring dom_ready and rejecting - // the returned deferred to get the proper message - if (!$('.o_payment_form').length) { - console.log("DOM doesn't contain '.o_payment_form'"); - return; - } - $('.o_payment_form').each(function () { - var $elem = $(this); - var form = new PaymentForm(null, $elem.data()); - form.attachTo($elem); + else { + // if there's no records linked to this payment method, then we delete it + tokenDelete(); + } + }, function (err, event) { + self.displayError( + _t('Server Error'), + _t("We are not able to delete your payment method at the moment.") + err.data.message + ); }); - }); - - return PaymentForm; + }, + /** + * Called when clicking on 'and more' to show more payment icon. + * + * @private + * @param {Event} ev + */ + onClickMorePaymentIcon: function (ev) { + ev.preventDefault(); + ev.stopPropagation(); + var $listItems = $(ev.currentTarget).parents('ul').children('li'); + var $moreItem = $(ev.currentTarget).parents('li'); + $listItems.removeClass('d-none'); + $moreItem.addClass('d-none'); + }, + /** + * Called when clicking on a radio button. + * + * @private + * @param {Event} ev + */ + radioClickEvent: function (ev) { + // radio button checked when we click on entire zone(body) of the payment acquirer + $(ev.currentTarget).find('input[type="radio"]').prop("checked", true); + this.updateNewPaymentDisplayStatus(); + }, +}); +return publicWidget.registry.PaymentForm; }); diff --git a/addons/portal/static/src/js/portal.js b/addons/portal/static/src/js/portal.js index f750bab1318..6a96ac5c967 100644 --- a/addons/portal/static/src/js/portal.js +++ b/addons/portal/static/src/js/portal.js @@ -1,48 +1,130 @@ odoo.define('portal.portal', function (require) { 'use strict'; - require('web.dom_ready'); - - - if (!$('.o_portal').length) { - return $.Deferred().reject("DOM doesn't contain '.o_portal'"); - } - - if ($('.o_portal_details').length) { - var state_options = $("select[name='state_id']:enabled option:not(:first)"); - $('.o_portal_details').on('change', "select[name='country_id']", function () { - var select = $("select[name='state_id']"); - state_options.detach(); - var displayed_state = state_options.filter("[data-country_id="+($(this).val() || 0)+"]"); - var nb = displayed_state.appendTo(select).show().size(); - select.parent().toggle(nb>=1); - }); - $('.o_portal_details').find("select[name='country_id']").change(); - } - - if ($('.o_portal_search_panel').length) { - $('.o_portal_search_panel .search-submit').click(function () { - var search = $.deparam(window.location.search.substring(1)); - search.search_in = $(".o_portal_search_panel .dropdown-item.active").attr("href").replace("#",""); - search.search = $(".o_portal_search_panel input[name='search']").val(); - window.location.search = $.param(search); - }); - - $('.o_portal_search_panel .dropdown-menu').find('.dropdown-item').click(function (e) { - e.preventDefault(); - $(this).parents('.dropdown-menu').find('.dropdown-item').removeClass('active'); - $(this).closest('.dropdown-item').addClass('active'); - var label = $(this).clone(); - label.find('span.nolabel').remove(); - $(".o_portal_search_panel span#search_label").text(label.text()); - }); - // init search label - $('.o_portal_search_panel .dropdown-menu').find('.dropdown-item.active').trigger('click'); - - $(".o_portal_search_panel input[name='search']").on('keyup', function (e) { - if (e.keyCode === 13) { - $('.o_portal_search_panel .search-submit').trigger('click'); - } - }); - } +var publicWidget = require('web.public.widget'); + +publicWidget.registry.portalDetails = publicWidget.Widget.extend({ + selector: '.o_portal_details', + read_events: { + 'change select[name="country_id"]': '_onCountryChange', + }, + + /** + * @override + */ + start: function () { + var def = this._super.apply(this, arguments); + if (this.editableMode) { + return; + } + + this.$state = this.$('select[name="state_id"]'); + this.$stateOptions = this.$state.filter(':enabled').find('option:not(:first)'); + this._adaptAddressForm(); + + return def; + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + */ + _adaptAddressForm: function () { + var $country = this.$('select[name="country_id"]'); + var countryID = ($country.val() || 0); + this.$stateOptions.detach(); + var $displayedState = this.$stateOptions.filter('[data-country_id=' + countryID + ']'); + var nb = $displayedState.appendTo(this.$state).show().size(); + this.$state.parent().toggle(nb >= 1); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onCountryChange: function () { + this._adaptAddressForm(); + }, +}); + +publicWidget.registry.portalSearchPanel = publicWidget.Widget.extend({ + selector: '.o_portal_search_panel', + read_events: { + 'click .search-submit': '_onSearchSubmitClick', + 'click .dropdown-item': '_onDropdownItemClick', + 'keyup input[name="search"]': '_onSearchInputKeyup', + }, + + /** + * @override + */ + start: function () { + var def = this._super.apply(this, arguments); + if (this.editableMode) { + return; + } + + this._adaptSearchLabel(this.$('.dropdown-item.active')); + + return def; + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + */ + _adaptSearchLabel: function (elem) { + var $label = $(elem).clone(); + $label.find('span.nolabel').remove(); + this.$('input[name="search"]').attr('placeholder', $label.text().trim()); + }, + /** + * @private + */ + _search: function () { + var search = $.deparam(window.location.search.substring(1)); + search['search_in'] = this.$('.dropdown-item.active').attr('href').replace('#', ''); + search['search'] = this.$('input[name="search"]').val(); + window.location.search = $.param(search); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onSearchSubmitClick: function () { + this._search(); + }, + /** + * @private + */ + _onDropdownItemClick: function (ev) { + ev.preventDefault(); + var $item = $(ev.currentTarget); + $item.closest('.dropdown-menu').find('.dropdown-item').removeClass('active'); + $item.addClass('active'); + + this._adaptSearchLabel(ev.currentTarget); + }, + /** + * @private + */ + _onSearchInputKeyup: function (ev) { + if (ev.keyCode === $.ui.keyCode.ENTER) { + this._search(); + } + }, +}); }); diff --git a/addons/portal/static/src/js/portal_chatter.js b/addons/portal/static/src/js/portal_chatter.js index bfd8fe95957..081a8f02ad3 100644 --- a/addons/portal/static/src/js/portal_chatter.js +++ b/addons/portal/static/src/js/portal_chatter.js @@ -1,12 +1,8 @@ -odoo.define('portal.chatter', function(require) { +odoo.define('portal.chatter', function (require) { 'use strict'; -var base = require('web_editor.base'); -var ajax = require('web.ajax'); var core = require('web.core'); -var dom = require('web.dom'); -var Widget = require('web.Widget'); -var rpc = require('web.rpc'); +var publicWidget = require('web.public.widget'); var time = require('web.time'); var qweb = core.qweb; @@ -18,12 +14,17 @@ var _t = core._t; * Display the composer (according to access right) * */ -var PortalComposer = Widget.extend({ +var PortalComposer = publicWidget.Widget.extend({ template: 'portal.Composer', + xmlDependencies: ['/portal/static/src/xml/portal_chatter.xml'], events: { - 'click .o_portal_chatter_composer_btn': '_onSubmitButtonClick', + 'click .o_portal_chatter_composer_btn': 'async _onSubmitButtonClick', }, - init: function(parent, options){ + + /** + * @constructor + */ + init: function (parent, options) { this._super.apply(this, arguments); this.options = _.defaults(options || {}, { 'allow_composer': true, @@ -33,12 +34,12 @@ var PortalComposer = Widget.extend({ 'res_model': false, 'res_id': false, }); - - // TODO simplify this using the 'async' keyword in the events - // property definition as soon as this widget is converted in - // frontend widget. - this._onSubmitButtonClick = dom.makeButtonHandler(this._onSubmitButtonClick); }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + /** * @private */ @@ -56,20 +57,23 @@ var PortalComposer = Widget.extend({ * - Display chatter: pager, total message, composer (according to access right) * - Provider API to filter displayed messages */ -var PortalChatter = Widget.extend({ +var PortalChatter = publicWidget.Widget.extend({ template: 'portal.Chatter', + xmlDependencies: ['/portal/static/src/xml/portal_chatter.xml'], events: { "click .o_portal_chatter_pager_btn": '_onClickPager', }, - xmlDependencies: ['/portal/static/src/xml/portal_chatter.xml'], - init: function(parent, options){ + /** + * @constructor + */ + init: function (parent, options) { var self = this; this.options = {}; this._super.apply(this, arguments); // underscorize the camelcased option keys - _.each(options, function(val, key) { + _.each(options, function (val, key) { self.options[_.str.underscored(key)] = val; }); // set default options @@ -92,22 +96,16 @@ var PortalChatter = Widget.extend({ this.set('message_count', this.options['message_count']); this.set('pager', {}); this.set('domain', this.options['domain']); - this._current_page = this.options['pager_start']; + this._currentPage = this.options['pager_start']; }, - willStart: function(){ - var self = this; - // load qweb template and init data + /** + * @override + */ + willStart: function () { return $.when( - rpc.query({ - route: '/mail/chatter_init', - params: this._messageFetchPrepareParams() - }), this._super.apply(this, arguments), - ).then(function(result){ - self.result = result; - self.options = _.extend(self.options, self.result['options'] || {}); - return result; - }); + this._chatterInit() + ); }, /** * @override @@ -115,9 +113,9 @@ var PortalChatter = Widget.extend({ start: function () { // bind events this.on("change:messages", this, this._renderMessages); - this.on("change:message_count", this, function(){ + this.on("change:message_count", this, function () { this._renderMessageCount(); - this.set('pager', this._pager(this._current_page)); + this.set('pager', this._pager(this._currentPage)); }); this.on("change:pager", this, this._renderPager); this.on("change:domain", this, this._onChangeDomain); @@ -149,12 +147,12 @@ var PortalChatter = Widget.extend({ * @param {Array} domain * @returns {Deferred} */ - messageFetch: function(domain){ + messageFetch: function (domain) { var self = this; - return rpc.query({ + return this._rpc({ route: '/mail/chatter_fetch', - params: self._messageFetchPrepareParams() - }).then(function(result){ + params: self._messageFetchPrepareParams(), + }).then(function (result) { self.set('messages', self.preprocessMessages(result['messages'])); self.set('message_count', result['message_count']); }); @@ -165,8 +163,8 @@ var PortalChatter = Widget.extend({ * @param {Array<Object>} * @returns {Array} */ - preprocessMessages: function(messages){ - _.each(messages, function(m){ + preprocessMessages: function (messages) { + _.each(messages, function (m) { m['author_avatar_url'] = _.str.sprintf('/web/image/%s/%s/author_avatar/50x50', 'mail.message', m.id); m['published_date_str'] = _.str.sprintf(_t('Published on %s'), moment(time.str_to_datetime(m.date)).format('MMMM Do YYYY, h:mm:ss a')); }); @@ -178,32 +176,47 @@ var PortalChatter = Widget.extend({ //-------------------------------------------------------------------------- /** + * @private + * @returns {Deferred} + */ + _chatterInit: function () { + var self = this; + return this._rpc({ + route: '/mail/chatter_init', + params: this._messageFetchPrepareParams() + }).then(function (result) { + self.result = result; + self.options = _.extend(self.options, self.result['options'] || {}); + return result; + }); + }, + /** * Change the current page by refreshing current domain * * @private * @param {Number} page * @param {Array} domain */ - _changeCurrentPage: function(page, domain){ - this._current_page = page; + _changeCurrentPage: function (page, domain) { + this._currentPage = page; var d = domain ? domain : _.clone(this.get('domain')); this.set('domain', d); // trigger fetch message }, - _messageFetchPrepareParams: function(){ + _messageFetchPrepareParams: function () { var self = this; var data = { 'res_model': this.options['res_model'], 'res_id': this.options['res_id'], 'limit': this.options['pager_step'], - 'offset': (this._current_page-1) * this.options['pager_step'], + 'offset': (this._currentPage - 1) * this.options['pager_step'], 'allow_composer': this.options['allow_composer'], }; // add token field to allow to post comment without being logged - if(self.options['token']){ + if (self.options['token']) { data['token'] = self.options['token']; } // add domain - if(this.get('domain')){ + if (this.get('domain')) { data['domain'] = this.get('domain'); } return data; @@ -215,34 +228,34 @@ var PortalChatter = Widget.extend({ * @param {Number} page * @returns {Object} */ - _pager: function(page){ - var page = page || 1; + _pager: function (page) { + page = page || 1; var total = this.get('message_count'); var scope = this.options['pager_scope']; var step = this.options['pager_step']; // Compute Pager - var page_count = Math.ceil(parseFloat(total) / step); + var pageCount = Math.ceil(parseFloat(total) / step); - var page = Math.max(1, Math.min(parseInt(page), page_count)); + page = Math.max(1, Math.min(parseInt(page), pageCount)); scope -= 1; - var pmin = Math.max(page - parseInt(Math.floor(scope/2)), 1); - var pmax = Math.min(pmin + scope, page_count); + var pmin = Math.max(page - parseInt(Math.floor(scope / 2)), 1); + var pmax = Math.min(pmin + scope, pageCount); - if(pmax - scope > 0){ + if (pmax - scope > 0) { pmin = pmax - scope; - }else{ + } else { pmin = 1; } var pages = []; - _.each(_.range(pmin, pmax+1), function(index){ + _.each(_.range(pmin, pmax + 1), function (index) { pages.push(index); }); return { - "page_count": page_count, + "page_count": pageCount, "offset": (page - 1) * step, "page": page, "page_start": pmin, @@ -252,13 +265,13 @@ var PortalChatter = Widget.extend({ "pages": pages }; }, - _renderMessages: function(){ + _renderMessages: function () { this.$('.o_portal_chatter_messages').html(qweb.render("portal.chatter_messages", {widget: this})); }, - _renderMessageCount: function(){ + _renderMessageCount: function () { this.$('.o_message_counter').replaceWith(qweb.render("portal.chatter_message_count", {widget: this})); }, - _renderPager: function(){ + _renderPager: function () { this.$('.o_portal_chatter_pager').replaceWith(qweb.render("portal.pager", {widget: this})); }, @@ -266,10 +279,10 @@ var PortalChatter = Widget.extend({ // Handlers //-------------------------------------------------------------------------- - _onChangeDomain: function(){ + _onChangeDomain: function () { var self = this; - this.messageFetch().then(function(){ - var p = self._current_page; + this.messageFetch().then(function () { + var p = self._currentPage; self.set('pager', self._pager(p)); }); }, @@ -277,24 +290,29 @@ var PortalChatter = Widget.extend({ * @private * @param {MouseEvent} event */ - _onClickPager: function(ev){ + _onClickPager: function (ev) { ev.preventDefault(); var page = $(ev.currentTarget).data('page'); this._changeCurrentPage(page); }, }); -base.ready().then(function () { - $('.o_portal_chatter').each(function (index) { - var $elem = $(this); - var mail_thread = new PortalChatter(null, $elem.data()); - mail_thread.appendTo($elem); - }); +publicWidget.registry.portalChatter = publicWidget.Widget.extend({ + selector: '.o_portal_chatter', + + /** + * @override + */ + start: function () { + var defs = [this._super.apply(this, arguments)]; + var chatter = new PortalChatter(this, this.$el.data()); + defs.push(chatter.appendTo(this.$el)); + return $.when.apply($, defs); + }, }); return { PortalComposer: PortalComposer, PortalChatter: PortalChatter, }; - }); diff --git a/addons/portal/static/src/js/portal_sidebar.js b/addons/portal/static/src/js/portal_sidebar.js index b0f2d3ed457..b565e27868b 100644 --- a/addons/portal/static/src/js/portal_sidebar.js +++ b/addons/portal/static/src/js/portal_sidebar.js @@ -1,21 +1,19 @@ odoo.define('portal.PortalSidebar', function (require) { -"use strict"; +'use strict'; -var config = require('web.config'); var core = require('web.core'); -var Widget = require('web.Widget'); +var publicWidget = require('web.public.widget'); var time = require('web.time'); var _t = core._t; -var PortalSidebar = Widget.extend({ +var PortalSidebar = publicWidget.Widget.extend({ /** * @override */ start: function () { - var self = this; - this._super.apply(this, arguments); this._setDelayLabel(); + return this._super.apply(this, arguments); }, //-------------------------------------------------------------------------- @@ -25,9 +23,10 @@ var PortalSidebar = Widget.extend({ /** * Set the due/delay information according to the given date * like : <span class="o_portal_sidebar_timeago" t-att-datetime="invoice.date_due"/> + * * @private */ - _setDelayLabel : function () { + _setDelayLabel: function () { var $sidebarTimeago = this.$el.find('.o_portal_sidebar_timeago'); _.each($sidebarTimeago, function (el) { var dateTime = moment(time.auto_str_to_date($(el).attr('datetime'))), @@ -35,7 +34,7 @@ var PortalSidebar = Widget.extend({ diff = dateTime.diff(today, 'days', true), displayStr; - if (diff === 0){ + if (diff === 0) { displayStr = _t('Due today'); } else if (diff > 0) { displayStr = _.str.sprintf(_t('Due in %d days'), Math.abs(diff)); @@ -54,10 +53,10 @@ var PortalSidebar = Widget.extend({ // open a new window with pdf for print in Firefox (in other system: http://printjs.crabbly.com) if ($.browser.mozilla) { window.open(href, '_blank'); - return ; + return; } if (!this.printContent) { - this.printContent = $('<iframe id="print_iframe_content" src="'+ href +'" style="display:none"></iframe>'); + this.printContent = $('<iframe id="print_iframe_content" src="' + href + '" style="display:none"></iframe>'); this.$el.append(this.printContent); this.printContent.on('load', function () { $(this).get(0).contentWindow.print(); diff --git a/addons/portal/static/src/js/portal_signature.js b/addons/portal/static/src/js/portal_signature.js index 60d999cfbf0..0174eeacdfe 100644 --- a/addons/portal/static/src/js/portal_signature.js +++ b/addons/portal/static/src/js/portal_signature.js @@ -1,195 +1,193 @@ odoo.define('portal.signature_form', function (require) { - 'use strict'; +'use strict'; + +var core = require('web.core'); +var publicWidget = require('web.public.widget'); +var NameAndSignature = require('web.name_and_signature').NameAndSignature; +var qweb = core.qweb; + +var _t = core._t; + +/** + * This widget is a signature request form. It uses + * @see NameAndSignature for the input fields, adds a submit + * button, and handles the RPC to save the result. + */ +var SignatureForm = publicWidget.Widget.extend({ + template: 'portal.portal_signature', + xmlDependencies: ['/portal/static/src/xml/portal_signature.xml'], + events: { + 'click .o_portal_sign_submit': 'async _onClickSignSubmit', + }, + custom_events: { + 'signature_changed': '_onChangeSignature', + }, + + /** + * Overridden to allow options. + * + * @constructor + * @param {Widget} parent + * @param {Object} options + * @param {string} options.callUrl - make RPC to this url + * @param {string} [options.sendLabel='Accept & Sign'] - label of the + * send button + * @param {Object} [options.rpcParams={}] - params for the RPC + * @param {Object} [options.nameAndSignatureOptions={}] - options for + * @see NameAndSignature.init() + */ + init: function (parent, options) { + this._super.apply(this, arguments); - var core = require('web.core'); - var dom = require('web.dom'); - var NameAndSignature = require('web.name_and_signature').NameAndSignature; - var qweb = core.qweb; - var Widget = require('web.Widget'); + this.csrf_token = odoo.csrf_token; - var _t = core._t; + this.callUrl = options.callUrl || ''; + this.rpcParams = options.rpcParams || {}; + this.sendLabel = options.sendLabel || _t("Accept & Sign"); + this.nameAndSignature = new NameAndSignature(this, + options.nameAndSignatureOptions || {}); + }, /** - * This widget is a signature request form. It uses - * @see NameAndSignature for the input fields, adds a submit - * button, and handles the RPC to save the result. + * Overridden to get the DOM elements + * and to insert the name and signature. + * + * @override */ - var SignatureForm = Widget.extend({ - template: 'portal.portal_signature', - xmlDependencies: ['/portal/static/src/xml/portal_signature.xml'], - events: { - 'click .o_portal_sign_submit': '_onClickSignSubmit', - }, - custom_events: { - 'signature_changed': '_onChangeSignature', - }, - - /** - * Overridden to allow options. - * - * @constructor - * @param {Widget} parent - * @param {Object} options - * @param {string} options.callUrl - make RPC to this url - * @param {string} [options.sendLabel='Accept & Sign'] - label of the - * send button - * @param {Object} [options.rpcParams={}] - params for the RPC - * @param {Object} [options.nameAndSignatureOptions={}] - options for - * @see NameAndSignature.init() - */ - init: function (parent, options) { - this._super.apply(this, arguments); - - this.csrf_token = odoo.csrf_token; - - this.callUrl = options.callUrl || ''; - this.rpcParams = options.rpcParams || {}; - this.sendLabel = options.sendLabel || _t("Accept & Sign"); - - this.nameAndSignature = new NameAndSignature(this, - options.nameAndSignatureOptions || {}); - - // debounce on click to not make multiple RPC if clicking too fast - this._onClickSignSubmit = dom.makeButtonHandler(this._onClickSignSubmit); - }, - /** - * Overridden to get the DOM elements - * and to insert the name and signature. - * - * @override - */ - start: function () { - this.$confirm_btn = this.$('.o_portal_sign_submit'); - this.$controls = this.$('.o_portal_sign_controls'); - this.nameAndSignature.replace(this.$('.o_web_sign_name_and_signature')); - return this._super.apply(this, arguments); - }, - - //---------------------------------------------------------------------- - // Public - //---------------------------------------------------------------------- - - /** - * Focuses the name. - * - * @see NameAndSignature.focusName(); - */ - focusName: function () { - this.nameAndSignature.focusName(); - }, - /** - * Resets the signature. - * - * @see NameAndSignature.resetSignature(); - */ - resetSignature: function () { - return this.nameAndSignature.resetSignature(); - }, - - //---------------------------------------------------------------------- - // Handlers - //---------------------------------------------------------------------- - - /** - * Handles click on the submit button. - * - * This will get the current name and signature and validate them. - * If they are valid, they are sent to the server, and the reponse is - * handled. If they are invalid, it will display the errors to the user. - * - * @private - * @param {Event} ev - * @returns {Deferred} - */ - _onClickSignSubmit: function (ev) { - var self = this; - ev.preventDefault(); - - if (!this.nameAndSignature.validateSignature()) { - return; - } + start: function () { + this.$confirm_btn = this.$('.o_portal_sign_submit'); + this.$controls = this.$('.o_portal_sign_controls'); + this.nameAndSignature.replace(this.$('.o_web_sign_name_and_signature')); + return this._super.apply(this, arguments); + }, - var name = this.nameAndSignature.getName(); - var signature = this.nameAndSignature.getSignatureImage()[1]; - - return this._rpc({ - route: this.callUrl, - params: _.extend(this.rpcParams, { - 'name': name, - 'signature': signature, - }), - }).then(function (data) { - if (data.error) { - self.$('.o_portal_sign_error_msg').remove(); - self.$controls.prepend(qweb.render('portal.portal_signature_error', {widget: data})); - } else if (data.success) { - var $success = qweb.render('portal.portal_signature_success', {widget: data}); - self.$el.empty().append($success); - } - if (data.force_refresh) { - if (data.redirect_url) { - window.location = data.redirect_url; - } else { - window.location.reload(); - } - // no resolve if we reload the page - return $.Deferred(); + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + /** + * Focuses the name. + * + * @see NameAndSignature.focusName(); + */ + focusName: function () { + this.nameAndSignature.focusName(); + }, + /** + * Resets the signature. + * + * @see NameAndSignature.resetSignature(); + */ + resetSignature: function () { + return this.nameAndSignature.resetSignature(); + }, + + //---------------------------------------------------------------------- + // Handlers + //---------------------------------------------------------------------- + + /** + * Handles click on the submit button. + * + * This will get the current name and signature and validate them. + * If they are valid, they are sent to the server, and the reponse is + * handled. If they are invalid, it will display the errors to the user. + * + * @private + * @param {Event} ev + * @returns {Deferred} + */ + _onClickSignSubmit: function (ev) { + var self = this; + ev.preventDefault(); + + if (!this.nameAndSignature.validateSignature()) { + return; + } + + var name = this.nameAndSignature.getName(); + var signature = this.nameAndSignature.getSignatureImage()[1]; + + return this._rpc({ + route: this.callUrl, + params: _.extend(this.rpcParams, { + 'name': name, + 'signature': signature, + }), + }).then(function (data) { + if (data.error) { + self.$('.o_portal_sign_error_msg').remove(); + self.$controls.prepend(qweb.render('portal.portal_signature_error', {widget: data})); + } else if (data.success) { + var $success = qweb.render('portal.portal_signature_success', {widget: data}); + self.$el.empty().append($success); + } + if (data.force_refresh) { + if (data.redirect_url) { + window.location = data.redirect_url; + } else { + window.location.reload(); } - }); - }, - /** - * Toggles the submit button depending on the signature state. - * - * @private - */ - _onChangeSignature: function () { - var isEmpty = this.nameAndSignature.isSignatureEmpty(); - this.$confirm_btn.prop('disabled', isEmpty); - }, - }); - - return { - SignatureForm: SignatureForm, - }; + // no resolve if we reload the page + return $.Deferred(); + } + }); + }, + /** + * Toggles the submit button depending on the signature state. + * + * @private + */ + _onChangeSignature: function () { + var isEmpty = this.nameAndSignature.isSignatureEmpty(); + this.$confirm_btn.prop('disabled', isEmpty); + }, }); -odoo.define('portal.signature_form_init', function (require) { -'use strict'; +publicWidget.registry.SignatureForm = publicWidget.Widget.extend({ + selector: '.o_portal_signature_form', -var SignatureForm = require('portal.signature_form').SignatureForm; -var rootWidget = require('root.widget'); -require('web.dom_ready'); - -$('.o_portal_signature_form').each(function () { - var hasBeenReset = false; - var $elem = $(this); - - var callUrl = $elem.data('call-url'); - var nameAndSignatureOptions = { - defaultName: $elem.data('default-name'), - mode: $elem.data('mode'), - displaySignatureRatio: $elem.data('signature-ratio'), - signatureType: $elem.data('signature-type'), - }; - var sendLabel = $elem.data('send-label'); - - var form = new SignatureForm(rootWidget, { - callUrl: callUrl, - nameAndSignatureOptions: nameAndSignatureOptions, - sendLabel: sendLabel, - }); - form.appendTo($elem); - // Correctly set up the signature area if it is inside a modal - $('.o_portal_signature_form').parents('.modal').on('shown.bs.modal', function (ev) { - if (!hasBeenReset) { - // Reset it only the first time it is open to get correct - // size. After we want to keep its content on reopen. - hasBeenReset = true; - form.resetSignature(); - } else { - form.focusName(); - } - }); + /** + * @private + */ + start: function () { + var hasBeenReset = false; + + var callUrl = this.$el.data('call-url'); + var nameAndSignatureOptions = { + defaultName: this.$el.data('default-name'), + mode: this.$el.data('mode'), + displaySignatureRatio: this.$el.data('signature-ratio'), + signatureType: this.$el.data('signature-type'), + }; + var sendLabel = this.$el.data('send-label'); + + var form = new SignatureForm(this, { + callUrl: callUrl, + nameAndSignatureOptions: nameAndSignatureOptions, + sendLabel: sendLabel, + }); + + // Correctly set up the signature area if it is inside a modal + this.$el.closest('.modal').on('shown.bs.modal', function (ev) { + if (!hasBeenReset) { + // Reset it only the first time it is open to get correct + // size. After we want to keep its content on reopen. + hasBeenReset = true; + form.resetSignature(); + } else { + form.focusName(); + } + }); + + return $.when( + this._super.apply(this, arguments), + form.appendTo(this.$el) + ); + }, }); +return { + SignatureForm: SignatureForm, +}; }); diff --git a/addons/project/static/src/js/portal_rating.js b/addons/project/static/src/js/portal_rating.js index 12740b9bbba..e1b51d9edc1 100644 --- a/addons/project/static/src/js/portal_rating.js +++ b/addons/project/static/src/js/portal_rating.js @@ -1,27 +1,32 @@ odoo.define('website_rating_project.rating', function (require) { 'use strict'; - var time = require('web.time'); - require('web.dom_ready'); +var time = require('web.time'); +var publicWidget = require('web.public.widget'); - if(!$('.o_portal_project_rating').length) { - return $.Deferred().reject("DOM doesn't contain '.o_portal_project_rating'"); - } +publicWidget.registry.ProjectRatingImage = publicWidget.Widget.extend({ + selector: '.o_portal_project_rating .o_rating_image', - /** - * Rating popover with some informations - */ - $('.o_portal_project_rating .o_rating_image').popover({ - placement: 'bottom', - trigger: 'hover', - html: 'true', - content: function () { - var id = $(this).data('id'); - var rating_date = $(this).data('rating-date'); - var base_date = time.auto_str_to_date(rating_date); - var duration = moment(base_date).fromNow(); - $("#rating_"+ id).find(".rating_timeduration").text(duration); - return $("#rating_"+ id).html(); - } - }); + /** + * @override + */ + start: function () { + this.$el.popover({ + placement: 'bottom', + trigger: 'hover', + html: 'true', + content: function () { + var $elem = $(this); + var id = $elem.data('id'); + var ratingDate = $elem.data('rating-date'); + var baseDate = time.auto_str_to_date(ratingDate); + var duration = moment(baseDate).fromNow(); + var $rating = $('#rating_' + id); + $rating.find('.rating_timeduration').text(duration); + return $rating.html(); + }, + }); + return this._super.apply(this, arguments); + }, +}); }); diff --git a/addons/sale/static/src/js/sale_portal_sidebar.js b/addons/sale/static/src/js/sale_portal_sidebar.js index 162a4ec5b2d..f9ba54ad661 100644 --- a/addons/sale/static/src/js/sale_portal_sidebar.js +++ b/addons/sale/static/src/js/sale_portal_sidebar.js @@ -1,43 +1,25 @@ -odoo.define('sale.SalePortalSidebar.instance', function (require) { -"use strict"; - -require('web.dom_ready'); -var SalePortalSidebar = require('sale.SalePortalSidebar'); - -if (!$('.o_portal_sale_sidebar').length) { - return $.Deferred().reject("DOM doesn't contain '.o_portal_sale_sidebar'"); -} - -var $spyWatch = $('body[data-target=".navspy"]'), - sale_portal_sidebar = new SalePortalSidebar($spyWatch); - -return sale_portal_sidebar.attachTo($('.o_portal_sale_sidebar')).then(function () { - return sale_portal_sidebar; -}); -}); - -//============================================================================== - odoo.define('sale.SalePortalSidebar', function (require) { -"use strict"; +'use strict'; +var publicWidget = require('web.public.widget'); var PortalSidebar = require('portal.PortalSidebar'); -var SalePortalSidebar = PortalSidebar.extend({ +publicWidget.registry.SalePortalSidebar = PortalSidebar.extend({ + selector: '.o_portal_sale_sidebar', + /** - * @override - * @param {Object} $watched_selector + * @constructor */ - init: function ($watched_selector) { + init: function (parent, options) { this._super.apply(this, arguments); this.authorizedTextTag = ['em', 'b', 'i', 'u']; - this.spyWatched = $watched_selector; + this.spyWatched = $('body[data-target=".navspy"]'); }, /** * @override */ start: function () { - this._super.apply(this, arguments); + var def = this._super.apply(this, arguments); var $spyWatcheElement = this.$el.find('[data-id="portal_sidebar"]'); this._setElementId($spyWatcheElement); // Nav Menu ScrollSpy @@ -47,6 +29,7 @@ var SalePortalSidebar = PortalSidebar.extend({ this.$('#o_sale_portal_paynow').trigger('click'); $.bbq.removeState('allow_payment'); } + return def; }, //-------------------------------------------------------------------------- @@ -129,6 +112,4 @@ var SalePortalSidebar = PortalSidebar.extend({ return rawText.join(' '); }, }); - -return SalePortalSidebar; }); diff --git a/addons/sale_management/static/src/js/sale_management.js b/addons/sale_management/static/src/js/sale_management.js index 77806630166..87d2c94c5d8 100644 --- a/addons/sale_management/static/src/js/sale_management.js +++ b/addons/sale_management/static/src/js/sale_management.js @@ -1,51 +1,47 @@ odoo.define('sale_management.sale_management', function (require) { 'use strict'; -require('web.dom_ready'); -var ajax = require('web.ajax'); -var Widget = require('web.Widget'); +var publicWidget = require('web.public.widget'); -if (!$('.o_portal_sale_sidebar').length) { - return $.Deferred().reject("DOM doesn't contain '.o_portal_sale_sidebar'"); -} +publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({ + selector: '.o_portal_sale_sidebar a.js_update_line_json', + events: { + 'click': '_onClick', + }, - // Add to SO button - var UpdateLineButton = Widget.extend({ - events: { - 'click' : 'onClick', - }, - onClick: function (ev) { - ev.preventDefault(); - var self = this; - var href = this.$el.attr("href"); - var order_id = href.match(/my\/orders\/([0-9]+)/); - var line_id = href.match(/update_line\/([0-9]+)/); - var params = { - 'line_id': line_id[1], - 'remove': self.$el.is('[href*="remove"]'), - 'unlink': self.$el.is('[href*="unlink"]'), - }; - var token = href.match(/token=(.*)/); - if (token) { - params.access_token = token; - } - var url = "/my/orders/" + parseInt(order_id[1]) + "/update_line"; - ajax.jsonRpc(url, 'call', params).then(function (data) { - if (!data) { - window.location.reload(); - } - self.$el.parents('.input-group:first').find('.js_quantity').val(data[0]); - $('[data-id="total_amount"]>span').html(data[1]); - }); - return false; - }, - }); - - var update_button_list = []; - $('a.js_update_line_json').each(function (index) { - var button = new UpdateLineButton(); - button.setElement($(this)).start(); - update_button_list.push(button); - }); + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + /** + * @private + */ + _onClick: function (ev) { + ev.preventDefault(); + var self = this; + var href = this.$el.attr("href"); + var orderID = href.match(/my\/orders\/([0-9]+)/); + var lineID = href.match(/update_line\/([0-9]+)/); + var params = { + 'line_id': lineID[1], + 'remove': self.$el.is('[href*="remove"]'), + 'unlink': self.$el.is('[href*="unlink"]'), + }; + var token = href.match(/token=(.*)/); + if (token) { + params['access_token'] = token; + } + var url = "/my/orders/" + parseInt(orderID[1]) + "/update_line"; + this._rpc({ + route: url, + params: params, + }).then(function (data) { + if (!data) { + window.location.reload(); + } + self.$el.closest('.input-group').find('.js_quantity').val(data[0]); + $('[data-id="total_amount"] > span').html(data[1]); + }); + }, +}); }); diff --git a/addons/web_unsplash/static/src/js/unsplash_beacon.js b/addons/web_unsplash/static/src/js/unsplash_beacon.js index eb19383b93f..c15ff027faf 100644 --- a/addons/web_unsplash/static/src/js/unsplash_beacon.js +++ b/addons/web_unsplash/static/src/js/unsplash_beacon.js @@ -1,24 +1,34 @@ odoo.define('web_unsplash.beacon', function (require) { 'use strict'; -require('web.dom_ready'); -var ajax = require('web.ajax'); +var publicWidget = require('web.public.widget'); -var unsplash_images = []; -_.each($('img[src*="/unsplash/"]'), function (img, index) { - // get image id from URL (`http://www.domain.com:1234/unsplash/xYdf5feoI/lion.jpg` -> `xYdf5feoI`) - unsplash_images.push(img.src.split('/unsplash/')[1].split('/')[0]); -}); -if (unsplash_images.length) { - ajax.jsonRpc('/web_unsplash/get_app_id', 'call').then(function (appID) { - if (appID) { - var unsplash_view_url = 'https://views.unsplash.com/v'; - $.get(unsplash_view_url, { - photo_id: unsplash_images.join(','), - app_id: appID, +publicWidget.registry.UnsplashBeacon = publicWidget.Widget.extend({ + // /!\ To adapt the day the beacon makes sense for backend customizations + selector: '#wrapwrap', + + /** + * @override + */ + start: function () { + var unsplashImages = _.map(this.$('img[src*="/unsplash/"]'), function (img) { + // get image id from URL (`http://www.domain.com:1234/unsplash/xYdf5feoI/lion.jpg` -> `xYdf5feoI`) + return img.src.split('/unsplash/')[1].split('/')[0]; + }); + if (unsplashImages.length) { + this._rpc({ + route: '/web_unsplash/get_app_id', + }).then(function (appID) { + if (!appID) { + return; + } + $.get('https://views.unsplash.com/v', { + 'photo_id': unsplashImages.join(','), + 'app_id': appID, + }); }); } - }); -} - + return this._super.apply(this, arguments); + }, +}); }); diff --git a/addons/website/static/src/js/content/website_root.js b/addons/website/static/src/js/content/website_root.js index ff398b2ff2d..ce4ebfec435 100644 --- a/addons/website/static/src/js/content/website_root.js +++ b/addons/website/static/src/js/content/website_root.js @@ -16,6 +16,7 @@ var WebsiteRoot = publicRootData.PublicRoot.extend({ 'click .js_publish_management .js_publish_btn': '_onPublishBtnClick', 'click .js_multi_website_switch': '_onWebsiteSwitch', 'click .js_multi_company_switch': '_onCompanySwitch', + 'shown.bs.modal': '_onModalShown', }), custom_events: _.extend({}, publicRootData.PublicRoot.prototype.custom_events || {}, { 'ready_to_clean_for_save': '_onAnimationStopDemand', @@ -167,6 +168,13 @@ var WebsiteRoot = publicRootData.PublicRoot.extend({ window.location.reload(true); }); }, + /** + * @private + * @param {Event} ev + */ + _onModalShown: function (ev) { + $(ev.target).addClass('modal_shown'); + }, }); return { diff --git a/addons/website/static/src/js/website.js b/addons/website/static/src/js/website.js deleted file mode 100644 index 66b410ee3c0..00000000000 --- a/addons/website/static/src/js/website.js +++ /dev/null @@ -1,9 +0,0 @@ -odoo.define('website.website_modal', function (require) { -'use strict'; - - require('web.dom_ready'); - - $('body').on('shown.bs.modal', function (e) { - $(e.target).addClass('modal_shown'); - }); -}); diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 6bf1c8614c1..5317f490ea8 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -63,7 +63,6 @@ </xpath> <xpath expr="//script[last()]" position="after"> <script type="text/javascript" src="/website/static/src/js/utils.js"/> - <script type="text/javascript" src="/website/static/src/js/website.js"/> <script type="text/javascript" src="/website/static/src/js/content/website_root.js"/> <script type="text/javascript" src="/website/static/src/js/content/compatibility.js"/> diff --git a/addons/website_mail/static/src/js/website_mail.js b/addons/website_mail/static/src/js/website_mail.js index 711df6ebbae..473ed4b4128 100644 --- a/addons/website_mail/static/src/js/website_mail.js +++ b/addons/website_mail/static/src/js/website_mail.js @@ -1,20 +1,13 @@ -odoo.define('website_mail.thread', function(require) { +odoo.define('website_mail.thread', function (require) { 'use strict'; -var ajax = require('web.ajax'); -var core = require('web.core'); - -var qweb = core.qweb; - -var PortalChatter = require('portal.chatter').PortalChatter; +var portalChatter = require('portal.chatter'); /** * Extends Frontend Chatter to handle rating */ -PortalChatter.include({ - _loadTemplates: function(){ - return $.when(this._super(), ajax.loadXML('/website_mail/static/src/xml/website_mail.xml', qweb)); - }, +portalChatter.PortalChatter.include({ + xmlDependencies: (portalChatter.PortalChatter.prototype.xmlDependencies || []) + .concat(['/website_mail/static/src/xml/website_mail.xml']), }); - }); diff --git a/addons/website_rating/static/src/js/website_mail.js b/addons/website_rating/static/src/js/website_mail.js index 6fd9a305e95..15b9b4ce503 100644 --- a/addons/website_rating/static/src/js/website_mail.js +++ b/addons/website_rating/static/src/js/website_mail.js @@ -1,335 +1,353 @@ -odoo.define('website_rating.thread', function(require) { - 'use strict'; - - var base = require('web_editor.base'); - var ajax = require('web.ajax'); - var core = require('web.core'); - var Widget = require('web.Widget'); - var rootWidget = require('web_editor.root_widget'); - - var session = require('web.session'); - - var qweb = core.qweb; - var _t = core._t; - - var PortalComposer = require('portal.chatter').PortalComposer; - var PortalChatter = require('portal.chatter').PortalChatter; - - var STAR_RATING_RATIO = 2; // conversion factor from the star (1-5) to the db rating range (1-10) +odoo.define('website_rating.thread', function (require) { +'use strict'; + +var core = require('web.core'); +var publicWidget = require('web.public.widget'); +var session = require('web.session'); +var portalChatter = require('portal.chatter'); + +var _t = core._t; + +var PortalComposer = portalChatter.PortalComposer; +var PortalChatter = portalChatter.PortalChatter; + +var STAR_RATING_RATIO = 2; // conversion factor from the star (1-5) to the db rating range (1-10) + +/** + * PortalComposer + * + * Extends Frontend Composer to handle rating submission + */ +PortalComposer.include({ + events: _.extend({}, PortalComposer.prototype.events, { + "mousemove .stars i": "_onMoveStar", + "mouseleave .stars i": "_onMoveOutStar", + "click .stars": "_onClickStar", + "mouseleave .stars": "_onMouseleaveStar", + }), /** - * PortalComposer - * - * Extends Frontend Composer to handle rating submission + * @constructor + */ + init: function (parent, options) { + this._super.apply(this, arguments); + + // apply ratio to default rating value + if (options.default_rating_value) { + options.default_rating_value = parseFloat(options.default_rating_value) / STAR_RATING_RATIO; + } + + // default options + this.options = _.defaults(this.options, { + 'default_message': false, + 'default_message_id': false, + 'default_rating_value': false, + 'force_submit_url': false, + }); + // star input widget + this.labels = { + '0': "", + '1': _t("I hate it"), + '2': _t("I don't like it"), + '3': _t("It's okay"), + '4': _t("I like it"), + '5': _t("I love it"), + }; + this.user_click = false; // user has click or not + this.set("star_value", this.options.default_rating_value); + this.on("change:star_value", this, this._onChangeStarValue); + }, + /** + * @override */ - PortalComposer.include({ - events: _.extend({}, PortalComposer.prototype.events, { - "mousemove .stars i" : "_onMoveStar", - "mouseleave .stars i" : "_onMoveOutStar", - "click .stars" : "_onClickStar", - "mouseleave .stars" : "_onMouseleaveStar", - }), + start: function () { + var self = this; + return this._super.apply(this, arguments).then(function () { + // rating stars + self.$input = self.$('input[name="rating_value"]'); + self.$star_list = self.$('.stars').find('i'); + self.set("star_value", self.options.default_rating_value); // set the default value to trigger the display of star widget + }); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- - init: function(parent, options){ - this._super.apply(this, arguments); + /** + * @private + */ + _onClickStar: function () { + this.user_click = true; + this.$input.val(this.get("star_value") * STAR_RATING_RATIO); + }, + /** + * @private + */ + _onChangeStarValue: function () { + var val = this.get("star_value"); + var index = Math.floor(val); + var decimal = val - index; + // reset the stars + this.$star_list.removeClass('fa-star fa-star-half-o').addClass('fa-star-o'); + + this.$('.stars').find("i:lt(" + index + ")").removeClass('fa-star-o fa-star-half-o').addClass('fa-star'); + if (decimal) { + this.$('.stars').find("i:eq(" + index + ")").removeClass('fa-star-o fa-star fa-star-half-o').addClass('fa-star-half-o'); + } + this.$('.rate_text .badge').text(this.labels[index]); + }, + /** + * @private + */ + _onMouseleaveStar: function () { + this.$('.rate_text').hide(); + }, + /** + * @private + * @param {MouseEvent} ev + */ + _onMoveStar: function (ev) { + var index = this.$('.stars i').index(ev.currentTarget); + this.$('.rate_text').show(); + this.set("star_value", index + 1); + }, + /** + * @private + */ + _onMoveOutStar: function () { + if (!this.user_click) { + this.set("star_value", parseInt(this.$input.val())); + } + this.user_click = false; + }, +}); - // apply ratio to default rating value - if (options.default_rating_value) { - options.default_rating_value = parseFloat(options.default_rating_value) / STAR_RATING_RATIO; - } +/** + * PortalChatter + * + * Extends Frontend Chatter to handle rating + */ +PortalChatter.include({ + events: _.extend({}, PortalChatter.prototype.events, { + "click .o_website_rating_select": "_onClickStarDomain", + "click .o_website_rating_select_text": "_onClickStarDomainReset", + }), + xmlDependencies: (PortalChatter.prototype.xmlDependencies || []) + .concat([ + '/website_rating/static/src/xml/website_mail.xml', + '/website_rating/static/src/xml/portal_chatter.xml' + ]), - // default options + /** + * @constructor + */ + init: function (parent, options) { + this._super.apply(this, arguments); + // options + if (!_.contains(this.options, 'display_rating')) { this.options = _.defaults(this.options, { - 'default_message': false, - 'default_message_id': false, - 'default_rating_value': false, - 'force_submit_url': false, - }); - // star input widget - this.labels = { - '0': "", - '1': _t("I hate it"), - '2': _t("I don't like it"), - '3': _t("It's okay"), - '4': _t("I like it"), - '5': _t("I love it"), - }; - this.user_click = false; // user has click or not - this.set("star_value", this.options.default_rating_value); - this.on("change:star_value", this, this._onChangeStarValue); - }, - start: function(){ - var self = this; - return this._super.apply(this, arguments).then(function(){ - // rating stars - self.$input = self.$('input[name="rating_value"]'); - self.$star_list = self.$('.stars').find('i'); - self.set("star_value", self.options.default_rating_value); // set the default value to trigger the display of star widget + 'display_rating': false, + 'rating_default_value': 0.0, }); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - * @param {MouseEvent} event - */ - _onClickStar: function(e){ - this.user_click = true; - this.$input.val(this.get("star_value") * STAR_RATING_RATIO); - }, - /** - * @private - */ - _onChangeStarValue: function(){ - var val = this.get("star_value"); - var index = Math.floor(val); - var decimal = val - index; - // reset the stars - this.$star_list.removeClass('fa-star fa-star-half-o').addClass('fa-star-o'); - - this.$('.stars').find("i:lt("+index+")").removeClass('fa-star-o fa-star-half-o').addClass('fa-star'); - if(decimal){ - this.$('.stars').find("i:eq("+(index)+")").removeClass('fa-star-o fa-star fa-star-half-o').addClass('fa-star-half-o'); - } - this.$('.rate_text .badge').text(this.labels[index]); - }, - _onMouseleaveStar: function(e){ - this.$('.rate_text').hide(); - }, - /** - * @private - * @param {MouseEvent} event - */ - _onMoveStar: function(e){ - var index = this.$('.stars i').index(e.currentTarget); - this.$('.rate_text').show(); - this.set("star_value", index+1); - }, - /** - * @private - */ - _onMoveOutStar: function(){ - if(!this.user_click){ - this.set("star_value", parseInt(this.$input.val())); - } - this.user_click = false; - }, - }); + } + // rating card + this.set('rating_card_values', {}); + this.set('rating_value', false); + this.on("change:rating_value", this, this._onChangeRatingDomain); + }, + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- /** - * PortalChatter + * Update the messages format * - * Extends Frontend Chatter to handle rating + * @param {Array<Object>} messages + * @returns {Array} */ - PortalChatter.include({ - events: _.extend({}, PortalChatter.prototype.events, { - "click .o_website_rating_select": "_onClickStarDomain", - "click .o_website_rating_select_text": "_onClickStarDomainReset", - }), - xmlDependencies: (PortalChatter.prototype.xmlDependencies || []).concat(['/website_rating/static/src/xml/website_mail.xml', '/website_rating/static/src/xml/portal_chatter.xml']), + preprocessMessages: function (messages) { + var self = this; + messages = this._super.apply(this, arguments); + if (this.options['display_rating']) { + _.each(messages, function (m) { + m['rating_value'] = self.roundToHalf(m['rating_value'] / STAR_RATING_RATIO); + }); + } + return messages; + }, + /** + * Round the given value with a precision of 0.5. + * + * Examples: + * - 1.2 --> 1.0 + * - 1.7 --> 1.5 + * - 1.9 --> 2.0 + * + * @param {Number} value + * @returns Number + **/ + roundToHalf: function (value) { + var converted = parseFloat(value); // Make sure we have a number + var decimal = (converted - parseInt(converted, 10)); + decimal = Math.round(decimal * 10); + if (decimal === 5) { + return (parseInt(converted, 10) + 0.5); + } + if ((decimal < 3) || (decimal > 7)) { + return Math.round(converted); + } else { + return (parseInt(converted, 10) + 0.5); + } + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- - init: function(parent, options){ - this._super.apply(this, arguments); - // options - if(!_.contains(this.options, 'display_rating')){ - this.options = _.defaults(this.options, { - 'display_rating': false, - 'rating_default_value': 0.0, - }); + /** + * @override + */ + _chatterInit: function () { + var self = this; + return this._super.apply(this, arguments).then(function (result) { + if (!result['rating_stats']) { + return; } - // rating card - this.set('rating_card_values', {}); - this.set('rating_value', false); - this.on("change:rating_value", this, this._onChangeRatingDomain); - }, - willStart: function(){ - var self = this; - return this._super.apply(this, arguments).then(function(result){ - // rating card - if(result['rating_stats']){ - var rating_data = { - 'avg': Math.round(result['rating_stats']['avg'] / STAR_RATING_RATIO * 100) / 100, - 'percent': [], - }; - _.each(_.keys(result['rating_stats']['percent']), function(rating){ - - if (rating % 2 == 0) { // is even number - rating_data['percent'].push({ - 'num': rating / STAR_RATING_RATIO, - 'percent': result['rating_stats']['percent'][rating], - }); - } + var ratingData = { + 'avg': Math.round(result['rating_stats']['avg'] / STAR_RATING_RATIO * 100) / 100, + 'percent': [], + }; + _.each(_.keys(result['rating_stats']['percent']), function (rating) { + if (rating % 2 === 0) { + ratingData['percent'].push({ + 'num': rating / STAR_RATING_RATIO, + 'percent': result['rating_stats']['percent'][rating], }); - self.set('rating_card_values', rating_data); } }); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * Update the messages format - * - * @param {Array<Object>} messages - * @returns {Array} - */ - preprocessMessages: function(messages){ - var self = this; - var messages = this._super.apply(this, arguments); - if(this.options['display_rating']){ - _.each(messages, function(m){ - m['rating_value'] = self.round_to_half(m['rating_value'] / STAR_RATING_RATIO); - }); - } - return messages; - }, - /** - * Round the given value with a precision of 0.5. - * - * Examples: - * - 1.2 --> 1.0 - * - 1.7 --> 1.5 - * - 1.9 --> 2.0 - * - * @param {Number} value - * @returns Number - **/ - round_to_half: function(value) { - var converted = parseFloat(value); // Make sure we have a number - var decimal = (converted - parseInt(converted, 10)); - decimal = Math.round(decimal * 10); - if(decimal === 5){ - return (parseInt(converted, 10)+0.5); - } - if((decimal < 3) || (decimal > 7)){ - return Math.round(converted); - }else{ - return (parseInt(converted, 10)+0.5); - } - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - _messageFetchPrepareParams: function(){ - var params = this._super.apply(this, arguments); - if(this.options['display_rating']){ - params['rating_include'] = true; - } - return params; - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- + self.set('rating_card_values', ratingData); + }); + }, + /** + * @override + */ + _messageFetchPrepareParams: function () { + var params = this._super.apply(this, arguments); + if (this.options['display_rating']) { + params['rating_include'] = true; + } + return params; + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- - /** - * @private - * @param {MouseEvent} event - */ - _onClickStarDomain: function(e){ - var $tr = this.$(e.currentTarget); - var num = $tr.data('star'); - if($tr.css('opacity') == 1){ - this.set('rating_value', num); - this.$('.o_website_rating_select').css({ - 'opacity': 0.5, - }); - this.$('.o_website_rating_select_text[data-star="'+num+'"]').css({ - 'visibility': 'visible', - 'opacity': 1, - }); - this.$('.o_website_rating_select[data-star="'+num+'"]').css({ - 'opacity': 1, - }); - } - }, - /** - * @private - * @param {MouseEvent} event - */ - _onClickStarDomainReset: function(e){ - e.stopPropagation(); - e.preventDefault(); - this.set('rating_value', false); - this.$('.o_website_rating_select_text').css('visibility', 'hidden'); + /** + * @private + * @param {MouseEvent} event + */ + _onClickStarDomain: function (e) { + var $tr = this.$(e.currentTarget); + var num = $tr.data('star'); + if ($tr.css('opacity') === '1') { + this.set('rating_value', num); this.$('.o_website_rating_select').css({ + 'opacity': 0.5, + }); + this.$('.o_website_rating_select_text[data-star="' + num + '"]').css({ + 'visibility': 'visible', 'opacity': 1, }); - }, - /** - * @private - */ - _onChangeRatingDomain: function(){ - var domain = []; - if(this.get('rating_value')){ - domain = [['rating_value', '=', this.get('rating_value') * STAR_RATING_RATIO]]; - } - this._changeCurrentPage(1, domain); - }, - }); - - - /** - * RatingPopupComposer - * - * Display the rating average with a static star widget, and open - * a popup with the portal composer when clicking on it. - **/ - var RatingPopupComposer = Widget.extend({ - template: 'website_rating.PopupComposer', - xmlDependencies: [ - '/portal/static/src/xml/portal_chatter.xml', - '/website_rating/static/src/xml/website_mail.xml', - '/website_rating/static/src/xml/portal_chatter.xml', - ], - - init: function(parent, options){ - this._super.apply(this, arguments); - this.rating_avg = Math.round(options['ratingAvg'] / STAR_RATING_RATIO * 100) / 100 || 0.0; - this.rating_total = options['ratingTotal'] || 0.0; - - this.options = _.defaults({}, options, { - 'token': false, - 'res_model': false, - 'res_id': false, - 'pid': 0, - 'display_composer': !session.is_website_user, - 'display_rating': true, - 'csrf_token': odoo.csrf_token, - 'user_id': session.user_id, + this.$('.o_website_rating_select[data-star="' + num + '"]').css({ + 'opacity': 1, }); - }, - /** - * @override - */ - start: function () { - var defs = []; - defs.push(this._super.apply(this, arguments)); - - // instanciate and insert composer widget - this._composer = new PortalComposer(this, this.options); - defs.push(this._composer.replace(this.$('.o_portal_chatter_composer'))); + } + }, + /** + * @private + * @param {MouseEvent} event + */ + _onClickStarDomainReset: function (e) { + e.stopPropagation(); + e.preventDefault(); + this.set('rating_value', false); + this.$('.o_website_rating_select_text').css('visibility', 'hidden'); + this.$('.o_website_rating_select').css({ + 'opacity': 1, + }); + }, + /** + * @private + */ + _onChangeRatingDomain: function () { + var domain = []; + if (this.get('rating_value')) { + domain = [['rating_value', '=', this.get('rating_value') * STAR_RATING_RATIO]]; + } + this._changeCurrentPage(1, domain); + }, +}); - return $.when.apply($, defs); - }, - }); +/** + * RatingPopupComposer + * + * Display the rating average with a static star widget, and open + * a popup with the portal composer when clicking on it. + **/ +var RatingPopupComposer = publicWidget.Widget.extend({ + template: 'website_rating.PopupComposer', + xmlDependencies: [ + '/portal/static/src/xml/portal_chatter.xml', + '/website_rating/static/src/xml/website_mail.xml', + '/website_rating/static/src/xml/portal_chatter.xml', + ], + + init: function (parent, options) { + this._super.apply(this, arguments); + this.rating_avg = Math.round(options['ratingAvg'] / STAR_RATING_RATIO * 100) / 100 || 0.0; + this.rating_total = options['ratingTotal'] || 0.0; + + this.options = _.defaults({}, options, { + 'token': false, + 'res_model': false, + 'res_id': false, + 'pid': 0, + 'display_composer': !session.is_website_user, + 'display_rating': true, + 'csrf_token': odoo.csrf_token, + 'user_id': session.user_id, + }); + }, + /** + * @override + */ + start: function () { + var defs = []; + defs.push(this._super.apply(this, arguments)); + // instanciate and insert composer widget + this._composer = new PortalComposer(this, this.options); + defs.push(this._composer.replace(this.$('.o_portal_chatter_composer'))); -base.ready().then(function () { - $('.o_rating_popup_composer').each(function (index) { - var $elem = $(this); - var rating_popup = new RatingPopupComposer(rootWidget, $elem.data()); - rating_popup.appendTo($elem); - }); + return $.when.apply($, defs); + }, }); +publicWidget.registry.RatingPopupComposer = publicWidget.Widget.extend({ + selector: '.o_rating_popup_composer', + /** + * @override + */ + start: function () { + var ratingPopup = new RatingPopupComposer(this, this.$el.data()); + return $.when( + this._super.apply(this, arguments), + ratingPopup.appendTo(this.$el) + ); + }, +}); }); diff --git a/addons/website_sale/static/src/js/website_sale_payment.js b/addons/website_sale/static/src/js/website_sale_payment.js index cc104fecc39..11edc7adc8d 100644 --- a/addons/website_sale/static/src/js/website_sale_payment.js +++ b/addons/website_sale/static/src/js/website_sale_payment.js @@ -1,18 +1,48 @@ odoo.define('website_sale.payment', function (require) { 'use strict'; -require('web.dom_ready'); - -var $checkbox = $("#checkbox_cgv"); -if (!$checkbox.length) { - return; -} -var $pay_button = $('button#o_payment_form_pay'); - -$checkbox.on('change', function (ev) { - $pay_button.data('disabled_reasons', $pay_button.data('disabled_reasons') || {}); - $pay_button.data('disabled_reasons').cgv = !ev.target.checked; - $pay_button.prop('disabled', _.contains($pay_button.data('disabled_reasons'), true)); +var publicWidget = require('web.public.widget'); + +publicWidget.registry.WebsiteSalePayment = publicWidget.Widget.extend({ + selector: '#wrapwrap:has(#checkbox_cgv)', + events: { + 'change #checkbox_cgv': '_onCGVCheckboxClick', + }, + + /** + * @override + */ + start: function () { + this.$checkbox = this.$('#checkbox_cgv'); + this.$payButton = $('button#o_payment_form_pay'); + this._adaptPayButton(); + return this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + */ + _adaptPayButton: function () { + var disabledReasons = this.$payButton.data('disabled_reasons') || {}; + disabledReasons.cgv = !this.$checkbox.prop('checked'); + this.$payButton.data('disabled_reasons', disabledReasons); + + this.$payButton.prop('disabled', _.contains(disabledReasons, true)); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onCGVCheckboxClick: function () { + this._adaptPayButton(); + }, }); -$checkbox.change(); }); |
