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(' '); - }); -} - + }, +}); }); 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"; +"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 core = require('web.core'); +var Dialog = require('web.Dialog'); +var publicWidget = require('web.public.widget'); - var PaymentForm = Widget.extend({ +var _t = core._t; - 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', - }, +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', + }, - init: function(parent, options) { - this._super.apply(this, arguments); - this.options = _.extend(options || {}, { - }); + /** + * @override + */ + start: function () { + this.options = _.extend(this.$el.data(), this.options); + this.updateNewPaymentDisplayStatus(); + $('[data-toggle="tooltip"]').tooltip(); + return this._super.apply(this, arguments); + }, - // 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); - }, + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- - start: function () { - this.updateNewPaymentDisplayStatus(); - $('[data-toggle="tooltip"]').tooltip(); - }, + /** + * @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); + } - 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('
' + _.str.escapeHTML("The value is invalid.") + '
'); - wrong_input = true; - } - else if ($(element).closest('div.form-group').hasClass('o_has_error')) { - wrong_input = true; - $(element).closest('div.form-group').append('
' + _.str.escapeHTML("The value is invalid.") + '
'); - } - }); - - if (wrong_input) { - return; - } - - $(button).attr('disabled', true); - $(button).children('.fa-plus-circle').removeClass('fa-plus-circle') - $(button).prepend(' '); - - 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' - 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(); - - 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(); - } + if ($checkedRadio.length === 0) { + return new Dialog(null, { + title: _t('Error: ') + _.str.escapeHTML(title), + size: 'medium', + $content: "

" + (_.str.escapeHTML(message) || "") + "

" , + buttons: [ + {text: _t('Ok'), close: true}]}).open(); + } else { + // removed if exist error message + this.$('#payment_error').remove(); + var messageResult = '
'; + if (title != '') { + messageResult = messageResult + '' + _.str.escapeHTML(title) + ':
'; } - else { - this.displayError( - _t('No payment method selected'), - _t('Please select a payment method.') - ); - } - }, - // 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; + messageResult = messageResult + _.str.escapeHTML(message) + '
'; + $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 = {}; - // 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); + $.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); + + // 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]; + + // 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 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 = '
' + _.str.escapeHTML("The value is invalid.") + '
'; - $(element).closest('div.form-group').append(message); + $(element).closest('div.form-group').append('
' + _.str.escapeHTML("The value is invalid.") + '
'); wrong_input = true; } else if ($(element).closest('div.form-group').hasClass('o_has_error')) { wrong_input = true; - var message = '
' + _.str.escapeHTML("The value is invalid.") + '
'; - $(element).closest('div.form-group').append(message); + $(element).closest('div.form-group').append('
' + _.str.escapeHTML("The value is invalid.") + '
'); } }); 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(' '); - // 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.

") + - 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 ); }); - }; - - rpc.query({ - 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 += '

' + sub.name + '

'; - }); - - content = $('

').html(_t('

This card is currently linked to the following records:

') + 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(); + // 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.") + ); } - }, function (err, event) { - self.displayError( - _t('Server Error'), - _t("We are not able to delete your payment method at the moment.") + err.data.message - ); - }); - }, - - // 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; } + 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; - // 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); - } - - if ($checkedRadio.length === 0) { - return new Dialog(null, { - title: _t('Error: ') + _.str.escapeHTML(title), - size: 'medium', - $content: "

" + (_.str.escapeHTML(message) || "") + "

" , - buttons: [ - {text: _t('Ok'), close: true}]}).open(); - } else { - // removed if exist error message - this.$('#payment_error').remove(); - var messageResult = '
'; - if (title != '') { - messageResult = messageResult + '' + _.str.escapeHTML(title) + ':
'; + 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 = '
' + _.str.escapeHTML("The value is invalid.") + '
'; + $(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 = '
' + _.str.escapeHTML("The value is invalid.") + '
'; + $(element).closest('div.form-group').append(message); } - messageResult = messageResult + _.str.escapeHTML(message) + '
'; - $acquirerForm.append(messageResult); - } - }, - 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; + 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(' '); + + // 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(); + + self.displayError( + _t('Server error'), + _t("We are not able to add your payment method at the moment.

") + + error.data.message + ); + }); } - $('.o_payment_form').each(function () { - var $elem = $(this); - var form = new PaymentForm(null, $elem.data()); - form.attachTo($elem); - }); - }); + 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); - return PaymentForm; + var tokenDelete = function () { + self._rpc({ + model: 'payment.token', + method: 'unlink', + args: [pm_id], + }).then(function (result) { + if (result === true) { + ev.target.closest('div').remove(); + } + }, function () { + self.displayError( + _t('Server Error'), + _t("We are not able to delete your payment method at the moment.") + ); + }); + }; + + 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 += '

' + sub.name + '

'; + }); + + content = $('

').html(_t('

This card is currently linked to the following records:

') + 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(); + } + }, function (err, event) { + self.displayError( + _t('Server Error'), + _t("We are not able to delete your payment method at the moment.") + err.data.message + ); + }); + }, + /** + * 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'); +var publicWidget = require('web.public.widget'); +publicWidget.registry.portalDetails = publicWidget.Widget.extend({ + selector: '.o_portal_details', + read_events: { + 'change select[name="country_id"]': '_onCountryChange', + }, - if (!$('.o_portal').length) { - return $.Deferred().reject("DOM doesn't contain '.o_portal'"); - } + /** + * @override + */ + start: function () { + var def = this._super.apply(this, arguments); + if (this.editableMode) { + return; + } - 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(); - } + this.$state = this.$('select[name="state_id"]'); + this.$stateOptions = this.$state.filter(':enabled').find('option:not(:first)'); + this._adaptAddressForm(); - 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); - }); + return def; + }, - $('.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'); + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- - $(".o_portal_search_panel input[name='search']").on('keyup', function (e) { - if (e.keyCode === 13) { - $('.o_portal_search_panel .search-submit').trigger('click'); - } - }); - } + /** + * @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} * @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')); }); @@ -177,6 +175,21 @@ var PortalChatter = Widget.extend({ // Private //-------------------------------------------------------------------------- + /** + * @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 * @@ -184,26 +197,26 @@ var PortalChatter = Widget.extend({ * @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 : + * * @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 = $(''); + this.printContent = $(''); 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'; - - 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'); - - 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 = 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; - } - - 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(); - } - }); - }, - /** - * 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, - }; -}); - -odoo.define('portal.signature_form_init', function (require) { 'use strict'; -var SignatureForm = require('portal.signature_form').SignatureForm; -var rootWidget = require('root.widget'); -require('web.dom_ready'); +var core = require('web.core'); +var publicWidget = require('web.public.widget'); +var NameAndSignature = require('web.name_and_signature').NameAndSignature; +var qweb = core.qweb; -$('.o_portal_signature_form').each(function () { - var hasBeenReset = false; - var $elem = $(this); +var _t = core._t; - 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'); +/** + * 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', + }, - 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(); + /** + * 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 || {}); + }, + /** + * 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; } - }); + + 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(); + } + }); + }, + /** + * Toggles the submit button depending on the signature state. + * + * @private + */ + _onChangeSignature: function () { + var isEmpty = this.nameAndSignature.isSignatureEmpty(); + this.$confirm_btn.prop('disabled', isEmpty); + }, }); +publicWidget.registry.SignatureForm = publicWidget.Widget.extend({ + selector: '.o_portal_signature_form', + + /** + * @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; + //-------------------------------------------------------------------------- + // 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(); } - 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); - }); - + 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 @@